This file is used to combine three datasets:

  • our dataset with 5 HS patients and 2 healthy donors
  • Wu dataset with 6 samples from 4 healthy donors
  • Takahashi dataset with 5 samples

We load each individual sample, remove melanocytes, and merge the remaining cells.

library(dplyr)
library(patchwork)
library(ggplot2)
library(ComplexHeatmap)
library(org.Hs.eg.db) # not in the Singularity container :(

.libPaths()
## [1] "/home/nf1/R/x86_64-pc-linux-gnu-library/3.6"
## [2] "/usr/local/lib/R/library"

Preparation

In this section, we set the global settings of the analysis. We will store data there :

save_name = "data3"
out_dir = "."
n_threads = 5 # for tSNE

We combine the three sample information :

sample_info_1 = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_sample_info.rds"))
sample_info_2 = readRDS(paste0(out_dir, "/../5_wu/1_metadata/wu_sample_info.rds"))
sample_info_3 = readRDS(paste0(out_dir, "/../6_takahashi/1_metadata/takahashi_sample_info.rds"))

column_to_keep = c("project_name", "sample_type", "sample_identifier", "color")

sample_info = rbind.data.frame(sample_info_1[, column_to_keep],
                               sample_info_2[, column_to_keep],
                               sample_info_3[, column_to_keep],
                               stringsAsFactors = FALSE)

graphics::pie(rep(1, nrow(sample_info)),
              col = sample_info$color,
              labels = sample_info$project_name)

Here are custom colors for each cell type :

color_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_color_markers.rds"))

data.frame(cell_type = names(color_markers),
           color = unlist(color_markers)) %>%
  ggplot2::ggplot(., aes(x = cell_type, y = 0, fill = cell_type)) +
  ggplot2::geom_point(pch = 21, size = 5) +
  ggplot2::scale_fill_manual(values = unlist(color_markers), breaks = names(color_markers)) +
  ggplot2::theme_classic() +
  ggplot2::theme(legend.position = "none",
                 axis.line = element_blank(),
                 axis.title = element_blank(),
                 axis.ticks = element_blank(),
                 axis.text.y = element_blank())

We load the markers and specific colors for each cell type :

cell_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_cell_markers.rds"))
lengths(cell_markers)
##      CD4 T cells      CD8 T cells Langerhans cells      macrophages 
##               13               13                9               10 
##          B cells          cuticle           cortex          medulla 
##               16               15               16               10 
##              IRS    proliferative              IBL              ORS 
##               16               20               15               16 
##              IFE             HFSC      melanocytes        sebocytes 
##               17               17               10                8

We load markers to display on the dotplot :

dotplot_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_dotplot_markers.rds"))
dotplot_markers
## $`CD4 T cells`
## [1] "CD3E" "CD4" 
## 
## $`CD8 T cells`
## [1] "CD3E" "CD8A"
## 
## $`Langerhans cells`
## [1] "CD207" "CPVL" 
## 
## $macrophages
## [1] "TREM2" "MSR1" 
## 
## $`B cells`
## [1] "CD79A" "CD79B"
## 
## $cuticle
## [1] "KRT32" "KRT35"
## 
## $cortex
## [1] "KRT31" "PRR9" 
## 
## $medulla
## [1] "BAMBI"   "ADLH1A3"
## 
## $IRS
## [1] "KRT71" "KRT73"
## 
## $proliferative
## [1] "TOP2A" "MCM5" 
## 
## $IBL
## [1] "KRT16" "KRT6C"
## 
## $ORS
## [1] "KRT15" "GPX2" 
## 
## $IFE
## [1] "SPINK5" "LY6D"  
## 
## $HFSC
## [1] "DIO2"   "TCEAL2"
## 
## $melanocytes
## [1] "DCT"   "MLANA"
## 
## $sebocytes
## [1] "CLMP"  "PPARG"

Make data3 dataset

Individual datasets

For each sample, we :

  • load individual dataset
  • look at cell annotation

We load individual datasets :

sobj_list = list()

# Our data
project_names_oi = sample_info_1$project_name
sobj_list[["here"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["here"]]) = project_names_oi

# Wu data
project_names_oi = sample_info_2$project_name
sobj_list[["wu"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../5_wu/2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["wu"]]) = project_names_oi

# Takahashi data
project_names_oi = sample_info_3$project_name
sobj_list[["takahashi"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../6_takahashi/2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["takahashi"]]) = project_names_oi

# Unlist
sobj_list = unlist(sobj_list, recursive = FALSE)

lapply(sobj_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  rbind(., colSums(.))
##                        [,1]  [,2]
## here.2021_31          27955  1043
## here.2021_36          27955   602
## here.2021_41          27955  2256
## here.2022_03          27955  3977
## here.2022_14          27955  2588
## here.2022_01          27955  1213
## here.2022_02          27955  2286
## wu.F18                27955  1372
## wu.F31B               27955  4786
## wu.F31W               27955  3520
## wu.F59                27955  2445
## wu.F62B               27955  3279
## wu.F62W               27955  2360
## takahashi.GSM3717034  12060  2421
## takahashi.GSM3717035  14154  2832
## takahashi.GSM3717036  17354  2656
## takahashi.GSM3717037  32738  5768
## takahashi.GSM3717038  32738  5874
##                      472459 51278

We represent cells in the tSNE :

name2D = "RNA_pca_20_tsne"

We look at cell type annotation for each dataset :

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  mytitle = as.character(unique(one_sobj$project_name))
  mysubtitle = ncol(one_sobj)
  
  p = Seurat::DimPlot(one_sobj, group.by = "cell_type",
                      reduction = name2D) +
    ggplot2::scale_color_manual(values = color_markers,
                                breaks = names(color_markers),
                                name = "Cell Type") +
    ggplot2::labs(title = mytitle,
                  subtitle = paste0(mysubtitle, " cells")) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5)) +
    Seurat::NoAxes()
  
  return(p)
})

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, ncol = 4) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

and clustering :

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  mytitle = as.character(unique(one_sobj$project_name))
  mysubtitle = ncol(one_sobj)
  
  p = Seurat::DimPlot(one_sobj, group.by = "seurat_clusters",
                      reduction = name2D, label = TRUE) +
    ggplot2::labs(title = mytitle,
                  subtitle = paste0(mysubtitle, " cells")) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5)) +
    Seurat::NoAxes() + Seurat::NoLegend()
  
  return(p)
})

patchwork::wrap_plots(plot_list, ncol = 4)

Melanocytes remomal

For each individual dataset, we remove melanocytes. First, we smooth cell type annotation at a cluster level :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  cluster_type = table(one_sobj$cell_type, one_sobj$seurat_clusters) %>%
    prop.table(., margin = 2) %>%
    apply(., 2, which.max)
  cluster_type = setNames(nm = names(cluster_type),
                          levels(one_sobj$cell_type)[cluster_type])
  
  one_sobj$cluster_type = cluster_type[one_sobj$seurat_clusters]
  
  ## Output
  return(one_sobj)
})

To locate melanocytes, we look at their score, cell type annotation, and clustering.

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  project_name = as.character(unique(one_sobj$project_name))
  plot_sublist = list()
  
  # Score
  plot_sublist[[1]] = Seurat::FeaturePlot(one_sobj, reduction = name2D,
                                          features = "score_melanocytes") +
    ggplot2::labs(title = project_name,
                  subtitle = "Melanocytes score") +
    Seurat::NoAxes() +
    ggplot2::scale_color_gradientn(colors = aquarius:::color_gene) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Cell type
  plot_sublist[[2]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "cell_type",
                                      order = "melanocytes") +
    ggplot2::scale_color_manual(values = c("purple", rep("gray92", length(color_markers) - 1)),
                                breaks = c("melanocytes", setdiff(names(color_markers), "melanocytes"))) +
    ggplot2::labs(title = "Cell type annotation",
                  subtitle = paste0(sum(one_sobj$cell_type == "melanocytes"),
                                    " melanocytes")) +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Clusters
  plot_sublist[[3]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "seurat_clusters",
                                      label = TRUE) +
    ggplot2::labs(title = "Clusters") +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Cluster type
  plot_sublist[[4]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "cluster_type") +
    ggplot2::scale_color_manual(values = c("purple", rep("gray92", length(color_markers) - 1)),
                                breaks = c("melanocytes", setdiff(names(color_markers), "melanocytes"))) +
    ggplot2::labs(title = "Cluster annotation",
                  subtitle = paste0(sum(one_sobj$cluster_type == "melanocytes"),
                                    " melanocytes")) +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  return(plot_sublist)
}) %>% unlist(., recursive = FALSE)

patchwork::wrap_plots(plot_list, ncol = 4)

We remove melanocytes based on cluster annotation :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  one_sobj$is_of_interest = (one_sobj$cluster_type != "melanocytes")
  
  if (sum(one_sobj$is_of_interest) > 0) {
    one_sobj = subset(one_sobj, is_of_interest == TRUE)
  } else {
    one_sobj = NA
  }
  
  one_sobj$is_of_interest = NULL
  return(one_sobj)
})

lapply(sobj_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  rbind(., colSums(.))
##                        [,1]  [,2]
## here.2021_31          27955   885
## here.2021_36          27955   528
## here.2021_41          27955  1982
## here.2022_03          27955  3457
## here.2022_14          27955  2422
## here.2022_01          27955   835
## here.2022_02          27955  2002
## wu.F18                27955  1372
## wu.F31B               27955  4624
## wu.F31W               27955  3520
## wu.F59                27955  2445
## wu.F62B               27955  3221
## wu.F62W               27955  2360
## takahashi.GSM3717034  12060  2278
## takahashi.GSM3717035  14154  2832
## takahashi.GSM3717036  17354  2527
## takahashi.GSM3717037  32738  5465
## takahashi.GSM3717038  32738  5667
##                      472459 48422

Re-annotation

We remove melanocytes from annotation :

cell_markers = cell_markers[names(cell_markers) != "melanocytes"]
color_markers = color_markers[names(color_markers) != "melanocytes"]
dotplot_markers = dotplot_markers[names(dotplot_markers) != "melanocytes"]

We re-annote cells for cell type, since melanocytes have been removed :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  # Remove old annotation
  one_sobj@meta.data[, grep(colnames(one_sobj@meta.data), pattern = "score", value = TRUE)] = NULL
  
  # Re-annot
  one_sobj = aquarius::cell_annot_custom(one_sobj,
                                         newname = "cell_type",
                                         markers = cell_markers,
                                         use_negative = TRUE,
                                         add_score = FALSE,
                                         verbose = TRUE)
  
  # Set factor levels
  one_sobj$cell_type = factor(one_sobj$cell_type, levels = names(cell_markers))
  
  return(one_sobj)
})

Combined dataset

Gene names homogenization

To which extent gene names are common between all datasets ?

gene_names = lapply(sobj_list, FUN = rownames)
obj_upset = ComplexHeatmap::make_comb_mat(gene_names, mode = "distinct")
ComplexHeatmap::UpSet(obj_upset)

For our data and Wu data, we have the correspondence between gene names and Ensembl IDs. For Takahashi data, we try to find them using bitr function from clusterProfiler package.

annotation = clusterProfiler::bitr(
  geneID   = unique(unlist(gene_names)),
  fromType = "SYMBOL",
  toType   = "ENSEMBL",
  OrgDb    = org.Hs.eg.db,
  drop = FALSE)

head(annotation)
##        SYMBOL         ENSEMBL
## 1 MIR1302-2HG            <NA>
## 2     FAM138A ENSG00000237613
## 3       OR4F5 ENSG00000186092
## 4  AL627309.1            <NA>
## 5  AL627309.3            <NA>
## 6  AL627309.4            <NA>

(Time to run : 0.53 s)

There is a lot of NA. Given that our data and Wu data were mapped using the same annotation, we complete the annotation table using the Ensembl ID we stored in each individual Seurat objects:

annotation = dplyr::left_join(
  x = annotation,
  y = sobj_list[["here.2021_31"]]@assays[["RNA"]]@meta.features[, c("Ensembl_ID", "gene_name")],
  by = c("SYMBOL" = "gene_name"))

head(annotation)
##        SYMBOL         ENSEMBL      Ensembl_ID
## 1 MIR1302-2HG            <NA> ENSG00000243485
## 2     FAM138A ENSG00000237613 ENSG00000237613
## 3       OR4F5 ENSG00000186092 ENSG00000186092
## 4  AL627309.1            <NA> ENSG00000238009
## 5  AL627309.3            <NA> ENSG00000239945
## 6  AL627309.4            <NA> ENSG00000241599

There is still a lot of data. We try to complete the dataframe using the biomaRt package:

httr::set_config(httr::config(ssl_verifypeer = FALSE))
mart = biomaRt::useEnsembl(biomart = "ensembl",
                           dataset = "hsapiens_gene_ensembl")
corresp = biomaRt::getBM(attributes = c("ensembl_gene_id", "hgnc_symbol"),
                         filters = "hgnc_symbol",
                         values = unique(unlist(gene_names)),
                         mart = mart)

head(corresp)
##   ensembl_gene_id hgnc_symbol
## 1 ENSG00000184389     A3GALT2
## 2 ENSG00000188984     AADACL3
## 3 ENSG00000204518     AADACL4
## 4 ENSG00000131584       ACAP3
## 5 ENSG00000162390      ACOT11
## 6 ENSG00000097021       ACOT7

(Time to run : 21.85 s)

We merge this information with the annotation table:

annotation = dplyr::left_join(
  x = annotation,
  y = corresp,
  by = c("SYMBOL" = "hgnc_symbol"))

head(annotation)
##        SYMBOL         ENSEMBL      Ensembl_ID ensembl_gene_id
## 1 MIR1302-2HG            <NA> ENSG00000243485 ENSG00000243485
## 2     FAM138A ENSG00000237613 ENSG00000237613 ENSG00000237613
## 3       OR4F5 ENSG00000186092 ENSG00000186092 ENSG00000186092
## 4  AL627309.1            <NA> ENSG00000238009            <NA>
## 5  AL627309.3            <NA> ENSG00000239945            <NA>
## 6  AL627309.4            <NA> ENSG00000241599            <NA>

We merge the two Ensembl columns:

annotation = annotation %>%
  dplyr::mutate(ENSEMBL_ID = ifelse(
    !is.na(ENSEMBL),
    yes = as.character(ENSEMBL),
    no = ifelse(!is.na(ensembl_gene_id),
                yes = as.character(ensembl_gene_id),
                no = as.character(Ensembl_ID)))) %>%
  dplyr::select(SYMBOL, ENSEMBL_ID) %>%
  unique()

head(annotation)
##        SYMBOL      ENSEMBL_ID
## 1 MIR1302-2HG ENSG00000243485
## 2     FAM138A ENSG00000237613
## 3       OR4F5 ENSG00000186092
## 4  AL627309.1 ENSG00000238009
## 5  AL627309.3 ENSG00000239945
## 6  AL627309.4 ENSG00000241599

How many NA data are remaining ?

table(is.na(annotation$ENSEMBL_ID))
## 
## FALSE  TRUE 
## 34854 14597

For which dataset Ensembl IDs are not available ?

genes_without_ensembl = annotation %>%
  dplyr::filter(is.na(ENSEMBL_ID)) %>%
  dplyr::pull(SYMBOL)

lapply(gene_names, FUN = function(sample_genes) {
  return(table(sample_genes %in% genes_without_ensembl))
}) %>% do.call(rbind, .) %>%
  `colnames<-`(c("ID available", "ID not available"))
##                      ID available ID not available
## here.2021_31                27935               20
## here.2021_36                27935               20
## here.2021_41                27935               20
## here.2022_03                27935               20
## here.2022_14                27935               20
## here.2022_01                27935               20
## here.2022_02                27935               20
## wu.F18                      27935               20
## wu.F31B                     27935               20
## wu.F31W                     27935               20
## wu.F59                      27935               20
## wu.F62B                     27935               20
## wu.F62W                     27935               20
## takahashi.GSM3717034        10976             1084
## takahashi.GSM3717035        12996             1158
## takahashi.GSM3717036        15384             1970
## takahashi.GSM3717037        20530            12208
## takahashi.GSM3717038        20530            12208

The fact that 20 ID are not available in our data and Wu data is due to genes sharing the same Ensembl ID… We will still keep all the genes available in these two sets of data. The goal here is to find a correspondence between gene names from Takahashi data and from our and Wu data, passing by Ensembl IDs.

Some genes share the same symbol but not the Ensembl ID. Since in Takahashi data, there are only symbols, we can not know the Ensembl ID to which they correspond. We make a choice to select the first Ensembl ID available for the symbol. For this, we select only the unique symbol in the annotation table.

table(duplicated(annotation$SYMBOL))
## 
## FALSE  TRUE 
## 46021  3430
dim(annotation)
## [1] 49451     2
annotation = annotation %>%
  dplyr::filter(!duplicated(SYMBOL))

dim(annotation)
## [1] 46021     2

Now, we can set symbol as row names:

rownames(annotation) = annotation$SYMBOL

head(annotation)
##                  SYMBOL      ENSEMBL_ID
## MIR1302-2HG MIR1302-2HG ENSG00000243485
## FAM138A         FAM138A ENSG00000237613
## OR4F5             OR4F5 ENSG00000186092
## AL627309.1   AL627309.1 ENSG00000238009
## AL627309.3   AL627309.3 ENSG00000239945
## AL627309.4   AL627309.4 ENSG00000241599

Extract count matrices

For each dataset, we extract the count matrices and metadata. The gene names in the count matrices will be convert to Ensembl ID, using our correspondence of Wu and our data, or the annotation table for Takahashi data.

our_correspondence = sobj_list[["here.2021_31"]]@assays[["RNA"]]@meta.features[, c("Ensembl_ID", "gene_name")]
our_correspondence$no_dup_gene_names = rownames(our_correspondence)
rownames(our_correspondence) = our_correspondence$Ensembl_ID

count_matrix_list = lapply(sobj_list, FUN = function(one_sobj) {
  project_name = unique(one_sobj$project_name)
  count_matrix = one_sobj@assays[["RNA"]]@counts
  
  if (project_name %in% sample_info_3$project_name) {
    # get Ensembl ID corresponding to gene names
    this_dataset_annot = annotation[rownames(count_matrix), ]
    
    # get gene names corresponding to Ensembl ID, using our correspondence (same dim)
    this_dataset_annot = dplyr::left_join(x = this_dataset_annot,
                                          y = our_correspondence,
                                          by = c("ENSEMBL_ID" = "Ensembl_ID"))
    
    # re set old gene names if none are available using our correspondence
    this_dataset_annot = this_dataset_annot %>%
      dplyr::mutate(no_dup_gene_names = ifelse(is.na(no_dup_gene_names),
                                               yes = SYMBOL,
                                               no = no_dup_gene_names))
    rownames(this_dataset_annot) = this_dataset_annot$SYMBOL # all in the count matrices
    
    # convert the row names of the count matrix
    rownames(count_matrix) = this_dataset_annot[rownames(count_matrix), "no_dup_gene_names"]
  }
  
  return(count_matrix)
})

lapply(count_matrix_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  `colnames<-`(c("Nb genes", "Nb cells"))
##                      Nb genes Nb cells
## here.2021_31            27955      885
## here.2021_36            27955      528
## here.2021_41            27955     1982
## here.2022_03            27955     3457
## here.2022_14            27955     2422
## here.2022_01            27955      835
## here.2022_02            27955     2002
## wu.F18                  27955     1372
## wu.F31B                 27955     4624
## wu.F31W                 27955     3520
## wu.F59                  27955     2445
## wu.F62B                 27955     3221
## wu.F62W                 27955     2360
## takahashi.GSM3717034    12060     2278
## takahashi.GSM3717035    14154     2832
## takahashi.GSM3717036    17354     2527
## takahashi.GSM3717037    32738     5465
## takahashi.GSM3717038    32738     5667

What is the aspect of the new upset plot ?

gene_names = lapply(count_matrix_list, FUN = rownames)
obj_upset = ComplexHeatmap::make_comb_mat(gene_names, mode = "distinct")
ComplexHeatmap::UpSet(obj_upset)

It almost did not change anything… Indeed:

table(rownames(sobj_list$takahashi.GSM3717037) %in% rownames(count_matrix_list$takahashi.GSM3717037))
## 
## FALSE  TRUE 
##    52 32686

So for the final dataset, we will extract only the common genes between all datasets. It corresponds to the first column in tne upset plot.

common_genes = Reduce(x = lapply(count_matrix_list, FUN = rownames),
                      f = intersect)

length(common_genes)
## [1] 7785

We subset all datasets to keep only these genes :

count_matrix_list = lapply(count_matrix_list, FUN = function(one_count_matrix) {
  one_count_matrix = one_count_matrix[common_genes, ]
  
  return(one_count_matrix)
})

lapply(count_matrix_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  `colnames<-`(c("Nb genes", "Nb cells"))
##                      Nb genes Nb cells
## here.2021_31             7785      885
## here.2021_36             7785      528
## here.2021_41             7785     1982
## here.2022_03             7785     3457
## here.2022_14             7785     2422
## here.2022_01             7785      835
## here.2022_02             7785     2002
## wu.F18                   7785     1372
## wu.F31B                  7785     4624
## wu.F31W                  7785     3520
## wu.F59                   7785     2445
## wu.F62B                  7785     3221
## wu.F62W                  7785     2360
## takahashi.GSM3717034     7785     2278
## takahashi.GSM3717035     7785     2832
## takahashi.GSM3717036     7785     2527
## takahashi.GSM3717037     7785     5465
## takahashi.GSM3717038     7785     5667

To merge all the count matrices, we add a prefix to each cell to avoid duplicated cell barcodes. The prefix is:

names(count_matrix_list) = names(count_matrix_list) %>%
  stringr::str_split(string = .,
                     pattern = "\\.") %>%
  lapply(., `[[`, 2) %>%
  unlist()
names(sobj_list) = names(count_matrix_list)

names(count_matrix_list)
##  [1] "2021_31"    "2021_36"    "2021_41"    "2022_03"    "2022_14"   
##  [6] "2022_01"    "2022_02"    "F18"        "F31B"       "F31W"      
## [11] "F59"        "F62B"       "F62W"       "GSM3717034" "GSM3717035"
## [16] "GSM3717036" "GSM3717037" "GSM3717038"

We add the prefix for each count matrix and merge them:

count_matrix_merge = lapply(names(count_matrix_list), FUN = function(one_project_name) {
  one_count_matrix = count_matrix_list[[one_project_name]]
  colnames(one_count_matrix) = paste0(one_project_name, "_", colnames(one_count_matrix))

  return(one_count_matrix)
}) %>% do.call(cbind.data.frame, .)

dim(count_matrix_merge)
## [1]  7785 48422
count_matrix_merge[c(1:3), c(1:3)]
##          2021_31_AAACGAAGTTGGCCTG-1 2021_31_AAAGGATCATTAAAGG-1
## NOC2L                             0                          3
## TNFRSF18                          2                          0
## SDF4                              1                          3
##          2021_31_AAAGGGCGTATCAGGG-1
## NOC2L                             0
## TNFRSF18                          2
## SDF4                              3

Extract metadata

Similarly, we build a big metadata table:

columns_of_interest = c("orig.ident", "nCount_RNA", "nFeature_RNA",
                        "log_nCount_RNA", "project_name", "sample_identifier",
                        "sample_type", "Seurat.Phase", "cyclone.Phase",
                        "percent.mt", "percent.rb", "cell_type" )

metadata_merge = lapply(names(sobj_list), FUN = function(one_project_name) {
  one_sobj = sobj_list[[one_project_name]]
  metadata = one_sobj@meta.data
  rownames(metadata) = paste0(one_project_name, "_", rownames(metadata))
  metadata = metadata[, columns_of_interest]
  
  return(metadata)
}) %>% do.call(rbind.data.frame, .)

dim(metadata_merge)
## [1] 48422    12
head(metadata_merge)
##                            orig.ident nCount_RNA nFeature_RNA log_nCount_RNA
## 2021_31_AAACGAAGTTGGCCTG-1    2021_31       6216         1872       8.734882
## 2021_31_AAAGGATCATTAAAGG-1    2021_31      44657         6174      10.706766
## 2021_31_AAAGGGCGTATCAGGG-1    2021_31      27344         5173      10.216252
## 2021_31_AAAGGTACAAATCGTC-1    2021_31       5776         1856       8.661467
## 2021_31_AAAGTCCCATACCAGT-1    2021_31      25356         4065      10.140771
## 2021_31_AAAGTCCGTGCCTACG-1    2021_31       8505         2346       9.048410
##                            project_name sample_identifier sample_type
## 2021_31_AAACGAAGTTGGCCTG-1      2021_31              HS_1          HS
## 2021_31_AAAGGATCATTAAAGG-1      2021_31              HS_1          HS
## 2021_31_AAAGGGCGTATCAGGG-1      2021_31              HS_1          HS
## 2021_31_AAAGGTACAAATCGTC-1      2021_31              HS_1          HS
## 2021_31_AAAGTCCCATACCAGT-1      2021_31              HS_1          HS
## 2021_31_AAAGTCCGTGCCTACG-1      2021_31              HS_1          HS
##                            Seurat.Phase cyclone.Phase percent.mt percent.rb
## 2021_31_AAACGAAGTTGGCCTG-1            S            G1  2.8314028   25.59524
## 2021_31_AAAGGATCATTAAAGG-1            S            G1  7.1769263   25.36221
## 2021_31_AAAGGGCGTATCAGGG-1          G2M            G1  4.3812171   20.74312
## 2021_31_AAAGGTACAAATCGTC-1           G1            G1  0.2077562   11.53047
## 2021_31_AAAGTCCCATACCAGT-1           G1            G1  0.1183152   17.64080
## 2021_31_AAAGTCCGTGCCTACG-1           G1            G1  0.1293357   32.82775
##                                cell_type
## 2021_31_AAACGAAGTTGGCCTG-1   CD4 T cells
## 2021_31_AAAGGATCATTAAAGG-1 proliferative
## 2021_31_AAAGGGCGTATCAGGG-1           ORS
## 2021_31_AAAGGTACAAATCGTC-1           IBL
## 2021_31_AAAGTCCCATACCAGT-1   macrophages
## 2021_31_AAAGTCCGTGCCTACG-1   CD8 T cells

Combined dataset

We generate a new Seurat object from the count matrix and the metadata:

dim(count_matrix_merge)
## [1]  7785 48422
dim(metadata_merge)
## [1] 48422    12
all.equal(rownames(metadata_merge), colnames(count_matrix_merge))
## [1] TRUE
sobj = Seurat::CreateSeuratObject(counts = count_matrix_merge,
                                  meta.data = metadata_merge)
sobj
## An object of class Seurat 
## 7785 features across 48422 samples within 1 assay 
## Active assay: RNA (7785 features, 0 variable features)

We add the correspondence between gene names and gene ID.

sobj@assays$RNA@meta.features = annotation[common_genes, ]

head(sobj@assays$RNA@meta.features)
##            SYMBOL      ENSEMBL_ID
## NOC2L       NOC2L ENSG00000188976
## TNFRSF18 TNFRSF18 ENSG00000186891
## SDF4         SDF4 ENSG00000078808
## UBE2J2     UBE2J2 ENSG00000160087
## AURKAIP1 AURKAIP1 ENSG00000175756
## CCNL2       CCNL2 ENSG00000221978

We remove what we will not use :

rm(sobj_list, annotation, corresp, our_correspondence, obj_upset,
   gene_names, mart, plot_list,
   sample_info_1, sample_info_2, sample_info_3, column_to_keep,
   count_matrix_merge, count_matrix_list, metadata_merge,
   column_to_keep, common_genes, columns_of_interest)

We keep a subset of meta.data and reset levels :

sobj$project_name = factor(sobj$project_name, levels = levels(sample_info$project_name))

summary(sobj@meta.data)
##       orig.ident      nCount_RNA      nFeature_RNA  log_nCount_RNA  
##  GSM3717038: 5667   Min.   :    20   Min.   :  20   Min.   : 2.996  
##  GSM3717037: 5465   1st Qu.:   163   1st Qu.: 127   1st Qu.: 5.094  
##  F31B      : 4624   Median :  4566   Median :1530   Median : 8.426  
##  F31W      : 3520   Mean   :  8501   Mean   :1798   Mean   : 7.636  
##  2022_03   : 3457   3rd Qu.: 12954   3rd Qu.:2923   3rd Qu.: 9.469  
##  F62B      : 3221   Max.   :139803   Max.   :7942   Max.   :11.848  
##  (Other)   :22468                                                   
##      project_name        sample_identifier       sample_type   
##  GSM3717038: 5667   Takahashi_HD_5: 5667   HS          : 9274  
##  GSM3717037: 5465   Takahashi_HD_4: 5465   HD          : 2837  
##  F31B      : 4624   Wu_HD_2       : 4624   Wu_HD       :17542  
##  F31W      : 3520   Wu_HD_3       : 3520   Takahashi_HD:18769  
##  2022_03   : 3457   HS_4          : 3457                       
##  F62B      : 3221   Wu_HD_5       : 3221                       
##  (Other)   :22468   (Other)       :22468                       
##     Seurat.Phase   cyclone.Phase        percent.mt       percent.rb   
##  G1       :30475   Length:48422       Min.   : 0.000   Min.   : 0.00  
##  G2M      : 7407   Class :character   1st Qu.: 1.756   1st Qu.:17.25  
##  S        :10406   Mode  :character   Median : 3.881   Median :23.58  
##  Undecided:  134                      Mean   : 4.750   Mean   :22.53  
##                                       3rd Qu.: 6.452   3rd Qu.:28.48  
##                                       Max.   :20.000   Max.   :50.00  
##                                                                       
##    cell_type    
##  IBL    :11134  
##  IFE    : 8379  
##  ORS    : 4816  
##  HFSC   : 4500  
##  cuticle: 3794  
##  cortex : 3064  
##  (Other):12735

Processing

We remove genes that are expressed in less than 5 cells :

sobj = aquarius::filter_features(sobj, min_cells = 5)
## [1]  7785 48422
## [1]  7785 48422
sobj
## An object of class Seurat 
## 7785 features across 48422 samples within 1 assay 
## Active assay: RNA (7785 features, 0 variable features)

We remove cells expressing less than 200 genes.

sobj = subset(sobj, nFeature_RNA > 250)
sobj
## An object of class Seurat 
## 7785 features across 35120 samples within 1 assay 
## Active assay: RNA (7785 features, 0 variable features)

Metadata

How many cells by sample ?

table(sobj$project_name)
## 
##    2021_31    2021_36    2021_41    2022_03    2022_14    2022_01    2022_02 
##        885        528       1982       3457       2422        835       2002 
##        F18       F31B       F31W        F59       F62B       F62W GSM3717034 
##       1372       4624       3520       2445       3221       2360         47 
## GSM3717035 GSM3717036 GSM3717037 GSM3717038 
##        252        418       3740       1010

We represent this information as a barplot :

aquarius::plot_barplot(df = table(sobj$project_name,
                                  sobj$cell_type) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("Sample", "Cell Type", "Number")),
                       x = "Sample", y = "Number", fill = "Cell Type",
                       position = position_fill()) +
  ggplot2::scale_fill_manual(values = unlist(color_markers),
                             breaks = names(color_markers),
                             name = "Cell Type")

This is the same barplot with another position :

aquarius::plot_barplot(df = table(sobj$project_name,
                                  sobj$cell_type) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("Sample", "Cell Type", "Number")),
                       x = "Sample", y = "Number", fill = "Cell Type",
                       position = position_stack()) +
  ggplot2::scale_fill_manual(values = unlist(color_markers),
                             breaks = names(color_markers),
                             name = "Cell Type")

Projection

We normalize the count matrix for remaining cells and select highly variable features :

sobj = Seurat::NormalizeData(sobj,
                             normalization.method = "LogNormalize")
sobj = Seurat::FindVariableFeatures(sobj, nfeatures = 2000)
sobj = Seurat::ScaleData(sobj)

sobj
## An object of class Seurat 
## 7785 features across 35120 samples within 1 assay 
## Active assay: RNA (7785 features, 2000 variable features)

We perform a PCA :

sobj = Seurat::RunPCA(sobj,
                      assay = "RNA",
                      reduction.name = "RNA_pca",
                      npcs = 100,
                      seed.use = 1337L)
sobj
## An object of class Seurat 
## 7785 features across 35120 samples within 1 assay 
## Active assay: RNA (7785 features, 2000 variable features)
##  1 dimensional reduction calculated: RNA_pca

We choose the number of dimensions such that they summarize 60 % of the variability :

stdev = sobj@reductions[["RNA_pca"]]@stdev
stdev_prop = cumsum(stdev)/sum(stdev)
ndims = which(stdev_prop > 0.60)[1]
ndims
## [1] 38

We can visualize this on the elbow plot :

elbow_p = Seurat::ElbowPlot(sobj, ndims = 100, reduction = "RNA_pca") +
  ggplot2::geom_point(x = ndims, y = stdev[ndims], col = "red")
x_text = ggplot_build(elbow_p)$layout$panel_params[[1]]$x$get_labels() %>% as.numeric()
elbow_p = elbow_p +
  ggplot2::scale_x_continuous(breaks = sort(c(x_text, ndims)), limits = c(0, 100))
x_color = ifelse(ggplot_build(elbow_p)$layout$panel_params[[1]]$x$get_labels() %>%
                   as.numeric() %>% round(., 2) == round(ndims, 2), "red", "black")
elbow_p = elbow_p +
  ggplot2::theme_classic() +
  ggplot2::theme(axis.text.x = element_text(color = x_color))

elbow_p

We generate a tSNE and a UMAP with 38 principal components :

sobj = Seurat::RunTSNE(sobj,
                       reduction = "RNA_pca",
                       dims = 1:ndims,
                       seed.use = 1337L,
                       num_threads = n_threads, # Rtsne::Rtsne option
                       reduction.name = paste0("RNA_pca_", ndims, "_tsne"))

sobj = Seurat::RunUMAP(sobj,
                       reduction = "RNA_pca",
                       dims = 1:ndims,
                       seed.use = 1337L,
                       reduction.name = paste0("RNA_pca_", ndims, "_umap"))

(Time to run : 139.3 s)

We can visualize the two representations :

tsne = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("RNA_pca_", ndims, "_tsne")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - tSNE") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5),
                 legend.position = "none")

umap = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("RNA_pca_", ndims, "_umap")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - UMAP") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

tsne | umap

Batch-effect correction

We remove sample specific effect on the pca using Harmony :

`%||%` = function(lhs, rhs) {
  if (!is.null(x = lhs)) {
    return(lhs)
  } else {
    return(rhs)
  }
}

set.seed(1337L)
sobj = harmony::RunHarmony(object = sobj,
                           group.by.vars = "project_name",
                           plot_convergence = TRUE,
                           reduction = "RNA_pca",
                           assay.use = "RNA",
                           reduction.save = "harmony",
                           max.iter.harmony = 50,
                           project.dim = FALSE)

(Time to run : 198.79 s)

From this batch-effect removed projection, we generate a tSNE and a UMAP.

sobj = Seurat::RunUMAP(sobj, 
                       seed.use = 1337L,
                       dims = 1:ndims,
                       reduction = "harmony",
                       reduction.name = paste0("harmony_", ndims, "_umap"),
                       reduction.key = paste0("harmony_", ndims, "umap_"))

sobj = Seurat::RunTSNE(sobj,
                       dims = 1:ndims,
                       seed.use = 1337L,
                       num_threads = n_threads, # Rtsne::Rtsne option
                       reduction = "harmony",
                       reduction.name = paste0("harmony_", ndims, "_tsne"),
                       reduction.key = paste0("harmony", ndims, "tsne_"))

(Time to run : 134.54 s)

We visualize the corrected projections :

tsne = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("harmony_", ndims, "_tsne")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - harmony - tSNE") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5),
                 legend.position = "none")

umap = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("harmony_", ndims, "_umap")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - harmony - UMAP") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

tsne | umap

We will keep the tSNE from harmony :

reduction = "harmony"
name2D = paste0("harmony_", ndims, "_tsne")

Clustering

We generate a clustering :

sobj = Seurat::FindNeighbors(sobj, reduction = reduction, dims = 1:ndims)
sobj = Seurat::FindClusters(sobj, resolution = 1.5)
## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
## 
## Number of nodes: 35120
## Number of edges: 1447874
## 
## Running Louvain algorithm...
## Maximum modularity in 10 random starts: 0.8757
## Number of communities: 36
## Elapsed time: 7 seconds
clusters_plot = Seurat::DimPlot(sobj, reduction = name2D, label = TRUE) +
  Seurat::NoAxes() + Seurat::NoLegend() +
  ggplot2::labs(title = "Clusters ID") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))
clusters_plot

Visualization

We represent the 4 quality metrics :

plot_list = Seurat::FeaturePlot(sobj, reduction = name2D,
                                combine = FALSE, pt.size = 0.25,
                                features = c("percent.mt", "percent.rb", "log_nCount_RNA", "nFeature_RNA"))
plot_list = lapply(plot_list, FUN = function(one_plot) {
  one_plot +
    Seurat::NoAxes() +
    ggplot2::scale_color_gradientn(colors = aquarius:::color_gene) +
    ggplot2::theme(aspect.ratio = 1)
})

patchwork::wrap_plots(plot_list, nrow = 1)

Clusters

We can represent clusters, split by sample of origin :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        group_by = "seurat_clusters",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_color = aquarius::gg_color_hue(length(levels(sobj$seurat_clusters))))

plot_list[[length(plot_list) + 1]] = clusters_plot +
  ggplot2::labs(title = "Cluster ID") &
  ggplot2::theme(plot.title = element_text(hjust = 0.5, size = 15))

patchwork::wrap_plots(plot_list, ncol = 4) &
  Seurat::NoLegend()

Cell type

We visualize cell type :

plot_list = lapply((c(paste0("RNA_pca_", ndims, "_tsne"),
                      paste0("RNA_pca_", ndims, "_umap"),
                      paste0("harmony_", ndims, "_tsne"),
                      paste0("harmony_", ndims, "_umap"))),
                   FUN = function(one_red) {
                     Seurat::DimPlot(sobj, group.by = "cell_type",
                                     reduction = one_red,
                                     cols = color_markers) +
                       Seurat::NoAxes() + ggplot2::ggtitle(one_red) +
                       ggplot2::theme(aspect.ratio = 1,
                                      plot.title = element_text(hjust = 0.5))
                   })

patchwork::wrap_plots(plot_list, nrow = 2) +
  patchwork::plot_layout(guides = "collect")

We make a representation split by origin to show cell types :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_by = "cell_type",
                                        group_color = color_markers)

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, ncol = 4) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

Cell cycle

We visualize cell cycle annotation, and BIRC5 and TOP2A expression levels :

plot_list = list()

# Seurat
plot_list[[1]] = Seurat::DimPlot(sobj, group.by = "Seurat.Phase",
                                 reduction = name2D) +
  Seurat::NoAxes() + ggplot2::labs(title = "Seurat annotation") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# cyclone
plot_list[[2]] = Seurat::DimPlot(sobj, group.by = "cyclone.Phase",
                                 reduction = name2D) +
  Seurat::NoAxes() + ggplot2::labs(title = "cyclone annotation") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# BIRC5
plot_list[[3]] = Seurat::FeaturePlot(sobj, features = "BIRC5",
                                     reduction = name2D) +
  ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# TOP2A
plot_list[[4]] = Seurat::FeaturePlot(sobj, features = "TOP2A",
                                     reduction = name2D) +
  ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

patchwork::wrap_plots(plot_list, ncol = 2)

Save

We save the Seurat object :

saveRDS(sobj, file = paste0(out_dir, "/", save_name, "_sobj.rds"))

R Session

show
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 24.04.1 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so
## LAPACK: /usr/local/lib/R/lib/libRlapack.so
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
##  [1] parallel  stats4    grid      stats     graphics  grDevices utils    
##  [8] datasets  methods   base     
## 
## other attached packages:
##  [1] org.Hs.eg.db_3.10.0   AnnotationDbi_1.48.0  IRanges_2.20.2       
##  [4] S4Vectors_0.24.4      Biobase_2.46.0        BiocGenerics_0.32.0  
##  [7] ComplexHeatmap_2.14.0 ggplot2_3.3.5         patchwork_1.1.2      
## [10] dplyr_1.0.7          
## 
## loaded via a namespace (and not attached):
##   [1] softImpute_1.4              graphlayouts_0.7.0         
##   [3] pbapply_1.4-2               lattice_0.20-41            
##   [5] haven_2.3.1                 vctrs_0.3.8                
##   [7] usethis_2.0.1               dynwrap_1.2.1              
##   [9] blob_1.2.1                  survival_3.2-13            
##  [11] prodlim_2019.11.13          dynutils_1.0.5             
##  [13] later_1.3.0                 DBI_1.1.1                  
##  [15] R.utils_2.11.0              SingleCellExperiment_1.8.0 
##  [17] rappdirs_0.3.3              uwot_0.1.8                 
##  [19] dqrng_0.2.1                 jpeg_0.1-8.1               
##  [21] zlibbioc_1.32.0             pspline_1.0-18             
##  [23] pcaMethods_1.78.0           mvtnorm_1.1-1              
##  [25] htmlwidgets_1.5.4           GlobalOptions_0.1.2        
##  [27] future_1.22.1               UpSetR_1.4.0               
##  [29] laeken_0.5.2                leiden_0.3.3               
##  [31] clustree_0.4.3              scater_1.14.6              
##  [33] irlba_2.3.3                 DEoptimR_1.0-9             
##  [35] tidygraph_1.1.2             Rcpp_1.0.9                 
##  [37] readr_2.0.2                 KernSmooth_2.23-17         
##  [39] carrier_0.1.0               promises_1.1.0             
##  [41] gdata_2.18.0                DelayedArray_0.12.3        
##  [43] limma_3.42.2                graph_1.64.0               
##  [45] RcppParallel_5.1.9          Hmisc_4.4-0                
##  [47] fs_1.5.2                    RSpectra_0.16-0            
##  [49] fastmatch_1.1-0             ranger_0.12.1              
##  [51] digest_0.6.25               png_0.1-7                  
##  [53] sctransform_0.2.1           cowplot_1.0.0              
##  [55] DOSE_3.12.0                 here_1.0.1                 
##  [57] TInGa_0.0.0.9000            ggraph_2.0.3               
##  [59] pkgconfig_2.0.3             GO.db_3.10.0               
##  [61] DelayedMatrixStats_1.8.0    gower_0.2.1                
##  [63] ggbeeswarm_0.6.0            iterators_1.0.12           
##  [65] DropletUtils_1.6.1          reticulate_1.26            
##  [67] clusterProfiler_3.14.3      SummarizedExperiment_1.16.1
##  [69] circlize_0.4.15             beeswarm_0.4.0             
##  [71] GetoptLong_1.0.5            xfun_0.35                  
##  [73] bslib_0.3.1                 zoo_1.8-10                 
##  [75] tidyselect_1.1.0            reshape2_1.4.4             
##  [77] purrr_0.3.4                 ica_1.0-2                  
##  [79] pcaPP_1.9-73                viridisLite_0.3.0          
##  [81] rtracklayer_1.46.0          rlang_1.0.2                
##  [83] hexbin_1.28.1               jquerylib_0.1.4            
##  [85] dyneval_0.9.9               glue_1.4.2                 
##  [87] RColorBrewer_1.1-2          matrixStats_0.56.0         
##  [89] stringr_1.4.0               lava_1.6.7                 
##  [91] europepmc_0.3               DESeq2_1.26.0              
##  [93] recipes_0.1.17              labeling_0.3               
##  [95] harmony_0.1.0               httpuv_1.5.2               
##  [97] class_7.3-17                BiocNeighbors_1.4.2        
##  [99] DO.db_2.9                   annotate_1.64.0            
## [101] jsonlite_1.7.2              XVector_0.26.0             
## [103] bit_4.0.4                   mime_0.9                   
## [105] aquarius_0.1.5              Rsamtools_2.2.3            
## [107] gridExtra_2.3               gplots_3.0.3               
## [109] stringi_1.4.6               processx_3.5.2             
## [111] gsl_2.1-6                   bitops_1.0-6               
## [113] cli_3.0.1                   batchelor_1.2.4            
## [115] RSQLite_2.2.0               randomForest_4.6-14        
## [117] tidyr_1.1.4                 data.table_1.14.2          
## [119] rstudioapi_0.13             org.Mm.eg.db_3.10.0        
## [121] GenomicAlignments_1.22.1    nlme_3.1-147               
## [123] qvalue_2.18.0               scran_1.14.6               
## [125] locfit_1.5-9.4              scDblFinder_1.1.8          
## [127] listenv_0.8.0               ggthemes_4.2.4             
## [129] gridGraphics_0.5-0          R.oo_1.24.0                
## [131] dbplyr_1.4.4                TTR_0.24.2                 
## [133] readxl_1.3.1                lifecycle_1.0.1            
## [135] timeDate_3043.102           ggpattern_0.3.1            
## [137] munsell_0.5.0               cellranger_1.1.0           
## [139] R.methodsS3_1.8.1           proxyC_0.1.5               
## [141] visNetwork_2.0.9            caTools_1.18.0             
## [143] codetools_0.2-16            GenomeInfoDb_1.22.1        
## [145] vipor_0.4.5                 lmtest_0.9-38              
## [147] msigdbr_7.5.1               htmlTable_1.13.3           
## [149] triebeard_0.3.0             lsei_1.3-0                 
## [151] xtable_1.8-4                ROCR_1.0-7                 
## [153] BiocManager_1.30.10         scatterplot3d_0.3-41       
## [155] abind_1.4-5                 farver_2.0.3               
## [157] parallelly_1.28.1           RANN_2.6.1                 
## [159] askpass_1.1                 GenomicRanges_1.38.0       
## [161] RcppAnnoy_0.0.16            tibble_3.1.5               
## [163] ggdendro_0.1-20             cluster_2.1.0              
## [165] future.apply_1.5.0          Seurat_3.1.5               
## [167] dendextend_1.15.1           Matrix_1.3-2               
## [169] ellipsis_0.3.2              prettyunits_1.1.1          
## [171] lubridate_1.7.9             ggridges_0.5.2             
## [173] igraph_1.2.5                RcppEigen_0.3.3.7.0        
## [175] fgsea_1.12.0                remotes_2.4.2              
## [177] scBFA_1.0.0                 destiny_3.0.1              
## [179] VIM_6.1.1                   testthat_3.1.0             
## [181] htmltools_0.5.2             BiocFileCache_1.10.2       
## [183] yaml_2.2.1                  utf8_1.1.4                 
## [185] plotly_4.9.2.1              XML_3.99-0.3               
## [187] ModelMetrics_1.2.2.2        e1071_1.7-3                
## [189] foreign_0.8-76              withr_2.5.0                
## [191] fitdistrplus_1.0-14         BiocParallel_1.20.1        
## [193] xgboost_1.4.1.1             bit64_4.0.5                
## [195] foreach_1.5.0               robustbase_0.93-9          
## [197] Biostrings_2.54.0           GOSemSim_2.13.1            
## [199] rsvd_1.0.3                  memoise_2.0.0              
## [201] evaluate_0.18               forcats_0.5.0              
## [203] rio_0.5.16                  geneplotter_1.64.0         
## [205] tzdb_0.1.2                  caret_6.0-86               
## [207] ps_1.6.0                    DiagrammeR_1.0.6.1         
## [209] curl_4.3                    fdrtool_1.2.15             
## [211] fansi_0.4.1                 highr_0.8                  
## [213] urltools_1.7.3              xts_0.12.1                 
## [215] GSEABase_1.48.0             acepack_1.4.1              
## [217] edgeR_3.28.1                checkmate_2.0.0            
## [219] scds_1.2.0                  cachem_1.0.6               
## [221] npsurv_0.4-0                babelgene_22.3             
## [223] rjson_0.2.20                openxlsx_4.1.5             
## [225] ggrepel_0.9.1               clue_0.3-60                
## [227] rprojroot_2.0.2             stabledist_0.7-1           
## [229] tools_3.6.3                 sass_0.4.0                 
## [231] nichenetr_1.1.1             magrittr_2.0.1             
## [233] RCurl_1.98-1.2              proxy_0.4-24               
## [235] car_3.0-11                  ape_5.3                    
## [237] ggplotify_0.0.5             xml2_1.3.2                 
## [239] httr_1.4.2                  assertthat_0.2.1           
## [241] rmarkdown_2.18              boot_1.3-25                
## [243] globals_0.14.0              R6_2.4.1                   
## [245] Rhdf5lib_1.8.0              nnet_7.3-14                
## [247] RcppHNSW_0.2.0              progress_1.2.2             
## [249] genefilter_1.68.0           statmod_1.4.34             
## [251] gtools_3.8.2                shape_1.4.6                
## [253] HDF5Array_1.14.4            BiocSingular_1.2.2         
## [255] rhdf5_2.30.1                splines_3.6.3              
## [257] AUCell_1.8.0                carData_3.0-4              
## [259] colorspace_1.4-1            generics_0.1.0             
## [261] base64enc_0.1-3             dynfeature_1.0.0           
## [263] smoother_1.1                gridtext_0.1.1             
## [265] pillar_1.6.3                tweenr_1.0.1               
## [267] sp_1.4-1                    ggplot.multistats_1.0.0    
## [269] rvcheck_0.1.8               GenomeInfoDbData_1.2.2     
## [271] plyr_1.8.6                  gtable_0.3.0               
## [273] zip_2.2.0                   knitr_1.41                 
## [275] latticeExtra_0.6-29         biomaRt_2.42.1             
## [277] fastmap_1.1.0               ADGofTest_0.3              
## [279] copula_1.0-0                doParallel_1.0.15          
## [281] vcd_1.4-8                   babelwhale_1.0.1           
## [283] openssl_1.4.1               scales_1.1.1               
## [285] backports_1.2.1             ipred_0.9-12               
## [287] enrichplot_1.6.1            hms_1.1.1                  
## [289] ggforce_0.3.1               Rtsne_0.15                 
## [291] shiny_1.7.1                 numDeriv_2016.8-1.1        
## [293] polyclip_1.10-0             lazyeval_0.2.2             
## [295] Formula_1.2-3               tsne_0.1-3                 
## [297] crayon_1.3.4                MASS_7.3-54                
## [299] pROC_1.16.2                 viridis_0.5.1              
## [301] dynparam_1.0.0              rpart_4.1-15               
## [303] zinbwave_1.8.0              compiler_3.6.3             
## [305] ggtext_0.1.0
LS0tCnRpdGxlOiAiSFMgcHJvamVjdCIKc3VidGl0bGU6ICJDb21iaW5lZCBkYXRhc2V0ICh1cyArIFd1ICsgVGFrYWhhc2hpKSIKYXV0aG9yOiAiQXVkcmV5IgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclWS0lbS0lZCcpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCi0tLQoKPHN0eWxlPgpib2R5IHsKdGV4dC1hbGlnbjoganVzdGlmeX0KPC9zdHlsZT4KCjwhLS0gQXV0b21hdGljYWxseSBjb21wdXRlcyBhbmQgcHJpbnRzIGluIHRoZSBvdXRwdXQgdGhlIHJ1bm5pbmcgdGltZSBmb3IgYW55IGNvZGUgY2h1bmsgLS0+CmBgYHtyLCBlY2hvPUZBTFNFfQojIGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL3JtYXJrZG93bi9pc3N1ZXMvMTQ1Mwpob29rcyA9IGtuaXRyOjprbml0X2hvb2tzJGdldCgpCmhvb2tfZm9sZGFibGUgPSBmdW5jdGlvbih0eXBlKSB7CiAgZm9yY2UodHlwZSkKICBmdW5jdGlvbih4LCBvcHRpb25zKSB7CiAgICByZXMgPSBob29rc1tbdHlwZV1dKHgsIG9wdGlvbnMpCiAgICAKICAgIGlmIChpc0ZBTFNFKG9wdGlvbnNbW3Bhc3RlMCgiZm9sZF8iLCB0eXBlKV1dKSkgcmV0dXJuKHJlcykKICAgIAogICAgcGFzdGUwKAogICAgICAiPGRldGFpbHM+PHN1bW1hcnk+IiwgInNob3ciLCAiPC9zdW1tYXJ5PlxuXG4iLAogICAgICByZXMsCiAgICAgICJcblxuPC9kZXRhaWxzPiIKICAgICkKICB9Cn0Ka25pdHI6OmtuaXRfaG9va3Mkc2V0KAogIG91dHB1dCA9IGhvb2tfZm9sZGFibGUoIm91dHB1dCIpLAogIHBsb3QgPSBob29rX2ZvbGRhYmxlKCJwbG90IiksCiAgdGltZV9pdCA9IGxvY2FsKHsKICAgIG5vdyA9IE5VTEwKICAgIGZ1bmN0aW9uKGJlZm9yZSwgb3B0aW9ucykgewogICAgICBpZiAob3B0aW9ucyR0aW1lX2l0KSB7CiAgICAgICAgaWYgKGJlZm9yZSkgewogICAgICAgICAgbm93IDw8LSBTeXMudGltZSgpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHJlcyA9IGRpZmZ0aW1lKFN5cy50aW1lKCksIG5vdywgdW5pdHMgPSAic2VjcyIpCiAgICAgICAgICBwYXN0ZSgiKFRpbWUgdG8gcnVuIDoiLCByb3VuZChyZXMsIGRpZ2l0cyA9IDIpLCAicykiKQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0pCikKYGBgCgo8IS0tIFNldCBkZWZhdWx0IHBhcmFtZXRlcnMgZm9yIGFsbCBjaHVua3MgLS0+CmBgYHtyLCBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQpzZXQuc2VlZCgxMzM3TCkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAjIGRpc3BsYXkgY29kZQogICAgICAgICAgICAgICAgICAgICAgIyBkaXNwbGF5IGNodW5rIG91dHB1dAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZm9sZF9vdXRwdXQgPSBGQUxTRSwgIyB1c2VmdWxsIGZvciBzZXNzaW9uSW5mbygpCiAgICAgICAgICAgICAgICAgICAgICBmb2xkX3Bsb3QgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgIyBmaWd1cmUgc2V0dGluZ3MKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICdjZW50ZXInLAogICAgICAgICAgICAgICAgICAgICAgZmlnLndpZHRoID0gMjAsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICMgc29tZXRoaW5nIGFib3V0IHNlZWQsIGNodW5rIGFuZCBSbWFya2Rvd24gY29tcGlsYXRpb24KICAgICAgICAgICAgICAgICAgICAgICMgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMzk0MTcwMDMvbG9uZy12ZWN0b3JzLW5vdC1zdXBwb3J0ZWQteWV0LWVycm9yLWluLXJtZC1idXQtbm90LWluLXItc2NyaXB0CiAgICAgICAgICAgICAgICAgICAgICAjIGNhY2hlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlLmxhenkgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICMgYWRkIHJ1bnRpbWUgYWZ0ZXIgY2h1bmsKICAgICAgICAgICAgICAgICAgICAgIHRpbWVfaXQgPSBGQUxTRSkKYGBgCgoKVGhpcyBmaWxlIGlzIHVzZWQgdG8gY29tYmluZSB0aHJlZSBkYXRhc2V0czoKCiogb3VyIGRhdGFzZXQgd2l0aCA1IEhTIHBhdGllbnRzIGFuZCAyIGhlYWx0aHkgZG9ub3JzCiogV3UgZGF0YXNldCB3aXRoIDYgc2FtcGxlcyBmcm9tIDQgaGVhbHRoeSBkb25vcnMKKiBUYWthaGFzaGkgZGF0YXNldCB3aXRoIDUgc2FtcGxlcwoKV2UgbG9hZCBlYWNoIGluZGl2aWR1YWwgc2FtcGxlLCByZW1vdmUgbWVsYW5vY3l0ZXMsIGFuZCBtZXJnZSB0aGUgcmVtYWluaW5nIGNlbGxzLgoKCmBgYHtyIGxpYnJhcnl9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkob3JnLkhzLmVnLmRiKSAjIG5vdCBpbiB0aGUgU2luZ3VsYXJpdHkgY29udGFpbmVyIDooCgoubGliUGF0aHMoKQpgYGAKCgojIFByZXBhcmF0aW9uCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHNldCB0aGUgZ2xvYmFsIHNldHRpbmdzIG9mIHRoZSBhbmFseXNpcy4gV2Ugd2lsbCBzdG9yZSBkYXRhIHRoZXJlIDoKCmBgYHtyIG91dF9kaXJ9CnNhdmVfbmFtZSA9ICJkYXRhMyIKb3V0X2RpciA9ICIuIgpuX3RocmVhZHMgPSA1ICMgZm9yIHRTTkUKYGBgCgoKV2UgY29tYmluZSB0aGUgdGhyZWUgc2FtcGxlIGluZm9ybWF0aW9uIDoKCmBgYHtyIGN1c3RvbV9wYWxldHRlX3NhbXBsZSwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDd9CnNhbXBsZV9pbmZvXzEgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLy4uLzFfbWV0YWRhdGEvaHNfaGRfc2FtcGxlX2luZm8ucmRzIikpCnNhbXBsZV9pbmZvXzIgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLy4uLzVfd3UvMV9tZXRhZGF0YS93dV9zYW1wbGVfaW5mby5yZHMiKSkKc2FtcGxlX2luZm9fMyA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vNl90YWthaGFzaGkvMV9tZXRhZGF0YS90YWthaGFzaGlfc2FtcGxlX2luZm8ucmRzIikpCgpjb2x1bW5fdG9fa2VlcCA9IGMoInByb2plY3RfbmFtZSIsICJzYW1wbGVfdHlwZSIsICJzYW1wbGVfaWRlbnRpZmllciIsICJjb2xvciIpCgpzYW1wbGVfaW5mbyA9IHJiaW5kLmRhdGEuZnJhbWUoc2FtcGxlX2luZm9fMVssIGNvbHVtbl90b19rZWVwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9pbmZvXzJbLCBjb2x1bW5fdG9fa2VlcF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfaW5mb18zWywgY29sdW1uX3RvX2tlZXBdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKZ3JhcGhpY3M6OnBpZShyZXAoMSwgbnJvdyhzYW1wbGVfaW5mbykpLAogICAgICAgICAgICAgIGNvbCA9IHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgIGxhYmVscyA9IHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSkKYGBgCgpIZXJlIGFyZSBjdXN0b20gY29sb3JzIGZvciBlYWNoIGNlbGwgdHlwZSA6CgpgYGB7ciBjb2xvcl9tYXJrZXJzLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDEsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpjb2xvcl9tYXJrZXJzID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8xX21ldGFkYXRhL2hzX2hkX2NvbG9yX21hcmtlcnMucmRzIikpCgpkYXRhLmZyYW1lKGNlbGxfdHlwZSA9IG5hbWVzKGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgIGNvbG9yID0gdW5saXN0KGNvbG9yX21hcmtlcnMpKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoLiwgYWVzKHggPSBjZWxsX3R5cGUsIHkgPSAwLCBmaWxsID0gY2VsbF90eXBlKSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQocGNoID0gMjEsIHNpemUgPSA1KSArCiAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdW5saXN0KGNvbG9yX21hcmtlcnMpLCBicmVha3MgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKV2UgbG9hZCB0aGUgbWFya2VycyBhbmQgc3BlY2lmaWMgY29sb3JzIGZvciBlYWNoIGNlbGwgdHlwZSA6CgpgYGB7ciBjZWxsX21hcmtlcnN9CmNlbGxfbWFya2VycyA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vMV9tZXRhZGF0YS9oc19oZF9jZWxsX21hcmtlcnMucmRzIikpCmxlbmd0aHMoY2VsbF9tYXJrZXJzKQpgYGAKCldlIGxvYWQgbWFya2VycyB0byBkaXNwbGF5IG9uIHRoZSBkb3RwbG90IDoKCmBgYHtyIGRvdHBsb3RfbWFya2Vyc30KZG90cGxvdF9tYXJrZXJzID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8xX21ldGFkYXRhL2hzX2hkX2RvdHBsb3RfbWFya2Vycy5yZHMiKSkKZG90cGxvdF9tYXJrZXJzCmBgYAoKIyBNYWtlIGByIHNhdmVfbmFtZWAgZGF0YXNldAoKIyMgSW5kaXZpZHVhbCBkYXRhc2V0cwoKRm9yIGVhY2ggc2FtcGxlLCB3ZSA6CgoqIGxvYWQgaW5kaXZpZHVhbCBkYXRhc2V0CiogbG9vayBhdCBjZWxsIGFubm90YXRpb24KCldlIGxvYWQgaW5kaXZpZHVhbCBkYXRhc2V0cyA6CgpgYGB7ciBzb2JqX2xpc3R9CnNvYmpfbGlzdCA9IGxpc3QoKQoKIyBPdXIgZGF0YQpwcm9qZWN0X25hbWVzX29pID0gc2FtcGxlX2luZm9fMSRwcm9qZWN0X25hbWUKc29ial9saXN0W1siaGVyZSJdXSA9IGxhcHBseShwcm9qZWN0X25hbWVzX29pLCBGVU4gPSBmdW5jdGlvbihvbmVfcHJvamVjdF9uYW1lKSB7CiAgc3Vic29iaiA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vMl9pbmRpdmlkdWFsL2RhdGFzZXRzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG9uZV9wcm9qZWN0X25hbWUsICJfc29ial9maWx0ZXJlZC5yZHMiKSkKICByZXR1cm4oc3Vic29iaikKfSkKbmFtZXMoc29ial9saXN0W1siaGVyZSJdXSkgPSBwcm9qZWN0X25hbWVzX29pCgojIFd1IGRhdGEKcHJvamVjdF9uYW1lc19vaSA9IHNhbXBsZV9pbmZvXzIkcHJvamVjdF9uYW1lCnNvYmpfbGlzdFtbInd1Il1dID0gbGFwcGx5KHByb2plY3RfbmFtZXNfb2ksIEZVTiA9IGZ1bmN0aW9uKG9uZV9wcm9qZWN0X25hbWUpIHsKICBzdWJzb2JqID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi81X3d1LzJfaW5kaXZpZHVhbC9kYXRhc2V0cy8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICBvbmVfcHJvamVjdF9uYW1lLCAiX3NvYmpfZmlsdGVyZWQucmRzIikpCiAgcmV0dXJuKHN1YnNvYmopCn0pCm5hbWVzKHNvYmpfbGlzdFtbInd1Il1dKSA9IHByb2plY3RfbmFtZXNfb2kKCiMgVGFrYWhhc2hpIGRhdGEKcHJvamVjdF9uYW1lc19vaSA9IHNhbXBsZV9pbmZvXzMkcHJvamVjdF9uYW1lCnNvYmpfbGlzdFtbInRha2FoYXNoaSJdXSA9IGxhcHBseShwcm9qZWN0X25hbWVzX29pLCBGVU4gPSBmdW5jdGlvbihvbmVfcHJvamVjdF9uYW1lKSB7CiAgc3Vic29iaiA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vNl90YWthaGFzaGkvMl9pbmRpdmlkdWFsL2RhdGFzZXRzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG9uZV9wcm9qZWN0X25hbWUsICJfc29ial9maWx0ZXJlZC5yZHMiKSkKICByZXR1cm4oc3Vic29iaikKfSkKbmFtZXMoc29ial9saXN0W1sidGFrYWhhc2hpIl1dKSA9IHByb2plY3RfbmFtZXNfb2kKCiMgVW5saXN0CnNvYmpfbGlzdCA9IHVubGlzdChzb2JqX2xpc3QsIHJlY3Vyc2l2ZSA9IEZBTFNFKQoKbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZGltKSAlPiUKICBkby5jYWxsKHJiaW5kLCAuKSAlPiUKICByYmluZCguLCBjb2xTdW1zKC4pKQpgYGAKCgpXZSByZXByZXNlbnQgY2VsbHMgaW4gdGhlIHRTTkUgOgoKYGBge3IgbmFtZTJEfQpuYW1lMkQgPSAiUk5BX3BjYV8yMF90c25lIgpgYGAKCgpXZSBsb29rIGF0IGNlbGwgdHlwZSBhbm5vdGF0aW9uIGZvciBlYWNoIGRhdGFzZXQgOgoKYGBge3IgY2VsbF90eXBlX3Byb2osIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gMjB9CnBsb3RfbGlzdCA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9zb2JqKSB7CiAgbXl0aXRsZSA9IGFzLmNoYXJhY3Rlcih1bmlxdWUob25lX3NvYmokcHJvamVjdF9uYW1lKSkKICBteXN1YnRpdGxlID0gbmNvbChvbmVfc29iaikKICAKICBwID0gU2V1cmF0OjpEaW1QbG90KG9uZV9zb2JqLCBncm91cC5ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJEKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JfbWFya2VycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkNlbGwgVHlwZSIpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSBteXRpdGxlLAogICAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMChteXN1YnRpdGxlLCAiIGNlbGxzIikpICsKICAgIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICBTZXVyYXQ6Ok5vQXhlcygpCiAgCiAgcmV0dXJuKHApCn0pCgpwbG90X2xpc3RbW2xlbmd0aChwbG90X2xpc3QpICsgMV1dID0gcGF0Y2h3b3JrOjpndWlkZV9hcmVhKCkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSA0KSArCiAgcGF0Y2h3b3JrOjpwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCgphbmQgY2x1c3RlcmluZyA6CgoKYGBge3IgY2x1c3RlcmluZ19wcm9qLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDIwfQpwbG90X2xpc3QgPSBsYXBwbHkoc29ial9saXN0LCBGVU4gPSBmdW5jdGlvbihvbmVfc29iaikgewogIG15dGl0bGUgPSBhcy5jaGFyYWN0ZXIodW5pcXVlKG9uZV9zb2JqJHByb2plY3RfbmFtZSkpCiAgbXlzdWJ0aXRsZSA9IG5jb2wob25lX3NvYmopCiAgCiAgcCA9IFNldXJhdDo6RGltUGxvdChvbmVfc29iaiwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwgbGFiZWwgPSBUUlVFKSArCiAgICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gbXl0aXRsZSwKICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSBwYXN0ZTAobXlzdWJ0aXRsZSwgIiBjZWxscyIpKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKQogIAogIHJldHVybihwKQp9KQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpCmBgYAoKIyMgTWVsYW5vY3l0ZXMgcmVtb21hbAoKRm9yIGVhY2ggaW5kaXZpZHVhbCBkYXRhc2V0LCB3ZSByZW1vdmUgbWVsYW5vY3l0ZXMuIEZpcnN0LCB3ZSBzbW9vdGggY2VsbCB0eXBlIGFubm90YXRpb24gYXQgYSBjbHVzdGVyIGxldmVsIDoKCmBgYHtyIHNtb290aF9hbm5vdGF0aW9ufQpzb2JqX2xpc3QgPSBsYXBwbHkoc29ial9saXN0LCBGVU4gPSBmdW5jdGlvbihvbmVfc29iaikgewogIGNsdXN0ZXJfdHlwZSA9IHRhYmxlKG9uZV9zb2JqJGNlbGxfdHlwZSwgb25lX3NvYmokc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgIHByb3AudGFibGUoLiwgbWFyZ2luID0gMikgJT4lCiAgICBhcHBseSguLCAyLCB3aGljaC5tYXgpCiAgY2x1c3Rlcl90eXBlID0gc2V0TmFtZXMobm0gPSBuYW1lcyhjbHVzdGVyX3R5cGUpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyhvbmVfc29iaiRjZWxsX3R5cGUpW2NsdXN0ZXJfdHlwZV0pCiAgCiAgb25lX3NvYmokY2x1c3Rlcl90eXBlID0gY2x1c3Rlcl90eXBlW29uZV9zb2JqJHNldXJhdF9jbHVzdGVyc10KICAKICAjIyBPdXRwdXQKICByZXR1cm4ob25lX3NvYmopCn0pCmBgYAoKVG8gbG9jYXRlIG1lbGFub2N5dGVzLCB3ZSBsb29rIGF0IHRoZWlyIHNjb3JlLCBjZWxsIHR5cGUgYW5ub3RhdGlvbiwgYW5kIGNsdXN0ZXJpbmcuCgpgYGB7ciBwbG90X2NlbGxfdHlwZSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA1MH0KcGxvdF9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICBwcm9qZWN0X25hbWUgPSBhcy5jaGFyYWN0ZXIodW5pcXVlKG9uZV9zb2JqJHByb2plY3RfbmFtZSkpCiAgcGxvdF9zdWJsaXN0ID0gbGlzdCgpCiAgCiAgIyBTY29yZQogIHBsb3Rfc3VibGlzdFtbMV1dID0gU2V1cmF0OjpGZWF0dXJlUGxvdChvbmVfc29iaiwgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJzY29yZV9tZWxhbm9jeXRlcyIpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSBwcm9qZWN0X25hbWUsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIk1lbGFub2N5dGVzIHNjb3JlIikgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYXF1YXJpdXM6Ojpjb2xvcl9nZW5lKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgCiAgIyBDZWxsIHR5cGUKICBwbG90X3N1Ymxpc3RbWzJdXSA9IFNldXJhdDo6RGltUGxvdChvbmVfc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiY2VsbF90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9ICJtZWxhbm9jeXRlcyIpICsKICAgIGdncGxvdDI6OnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJwdXJwbGUiLCByZXAoImdyYXk5MiIsIGxlbmd0aChjb2xvcl9tYXJrZXJzKSAtIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCJtZWxhbm9jeXRlcyIsIHNldGRpZmYobmFtZXMoY29sb3JfbWFya2VycyksICJtZWxhbm9jeXRlcyIpKSkgKwogICAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDZWxsIHR5cGUgYW5ub3RhdGlvbiIsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKHN1bShvbmVfc29iaiRjZWxsX3R5cGUgPT0gIm1lbGFub2N5dGVzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIgbWVsYW5vY3l0ZXMiKSkgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAKICAjIENsdXN0ZXJzCiAgcGxvdF9zdWJsaXN0W1szXV0gPSBTZXVyYXQ6OkRpbVBsb3Qob25lX3NvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFKSArCiAgICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkNsdXN0ZXJzIikgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAKICAjIENsdXN0ZXIgdHlwZQogIHBsb3Rfc3VibGlzdFtbNF1dID0gU2V1cmF0OjpEaW1QbG90KG9uZV9zb2JqLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJjbHVzdGVyX3R5cGUiKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicHVycGxlIiwgcmVwKCJncmF5OTIiLCBsZW5ndGgoY29sb3JfbWFya2VycykgLSAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygibWVsYW5vY3l0ZXMiLCBzZXRkaWZmKG5hbWVzKGNvbG9yX21hcmtlcnMpLCAibWVsYW5vY3l0ZXMiKSkpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSAiQ2x1c3RlciBhbm5vdGF0aW9uIiwKICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSBwYXN0ZTAoc3VtKG9uZV9zb2JqJGNsdXN0ZXJfdHlwZSA9PSAibWVsYW5vY3l0ZXMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBtZWxhbm9jeXRlcyIpKSArCiAgICBTZXVyYXQ6Ok5vQXhlcygpICsgU2V1cmF0OjpOb0xlZ2VuZCgpICsKICAgIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQogIAogIHJldHVybihwbG90X3N1Ymxpc3QpCn0pICU+JSB1bmxpc3QoLiwgcmVjdXJzaXZlID0gRkFMU0UpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gNCkKYGBgCgpXZSByZW1vdmUgbWVsYW5vY3l0ZXMgYmFzZWQgb24gY2x1c3RlciBhbm5vdGF0aW9uIDoKCmBgYHtyIHJlbW92ZV9tZWxhbm9jeXRlc30Kc29ial9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICBvbmVfc29iaiRpc19vZl9pbnRlcmVzdCA9IChvbmVfc29iaiRjbHVzdGVyX3R5cGUgIT0gIm1lbGFub2N5dGVzIikKICAKICBpZiAoc3VtKG9uZV9zb2JqJGlzX29mX2ludGVyZXN0KSA+IDApIHsKICAgIG9uZV9zb2JqID0gc3Vic2V0KG9uZV9zb2JqLCBpc19vZl9pbnRlcmVzdCA9PSBUUlVFKQogIH0gZWxzZSB7CiAgICBvbmVfc29iaiA9IE5BCiAgfQogIAogIG9uZV9zb2JqJGlzX29mX2ludGVyZXN0ID0gTlVMTAogIHJldHVybihvbmVfc29iaikKfSkKCmxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGRpbSkgJT4lCiAgZG8uY2FsbChyYmluZCwgLikgJT4lCiAgcmJpbmQoLiwgY29sU3VtcyguKSkKYGBgCgojIyBSZS1hbm5vdGF0aW9uCgpXZSByZW1vdmUgbWVsYW5vY3l0ZXMgZnJvbSBhbm5vdGF0aW9uIDoKCmBgYHtyIHJlbW92ZV9mcm9tX2Fubm90fQpjZWxsX21hcmtlcnMgPSBjZWxsX21hcmtlcnNbbmFtZXMoY2VsbF9tYXJrZXJzKSAhPSAibWVsYW5vY3l0ZXMiXQpjb2xvcl9tYXJrZXJzID0gY29sb3JfbWFya2Vyc1tuYW1lcyhjb2xvcl9tYXJrZXJzKSAhPSAibWVsYW5vY3l0ZXMiXQpkb3RwbG90X21hcmtlcnMgPSBkb3RwbG90X21hcmtlcnNbbmFtZXMoZG90cGxvdF9tYXJrZXJzKSAhPSAibWVsYW5vY3l0ZXMiXQpgYGAKCldlIHJlLWFubm90ZSBjZWxscyBmb3IgY2VsbCB0eXBlLCBzaW5jZSBtZWxhbm9jeXRlcyBoYXZlIGJlZW4gcmVtb3ZlZCA6CgpgYGB7ciByZV9hbm5vdH0Kc29ial9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICAjIFJlbW92ZSBvbGQgYW5ub3RhdGlvbgogIG9uZV9zb2JqQG1ldGEuZGF0YVssIGdyZXAoY29sbmFtZXMob25lX3NvYmpAbWV0YS5kYXRhKSwgcGF0dGVybiA9ICJzY29yZSIsIHZhbHVlID0gVFJVRSldID0gTlVMTAogIAogICMgUmUtYW5ub3QKICBvbmVfc29iaiA9IGFxdWFyaXVzOjpjZWxsX2Fubm90X2N1c3RvbShvbmVfc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXduYW1lID0gImNlbGxfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFya2VycyA9IGNlbGxfbWFya2VycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VfbmVnYXRpdmUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9zY29yZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFKQogIAogICMgU2V0IGZhY3RvciBsZXZlbHMKICBvbmVfc29iaiRjZWxsX3R5cGUgPSBmYWN0b3Iob25lX3NvYmokY2VsbF90eXBlLCBsZXZlbHMgPSBuYW1lcyhjZWxsX21hcmtlcnMpKQogIAogIHJldHVybihvbmVfc29iaikKfSkKYGBgCgojIyBDb21iaW5lZCBkYXRhc2V0CgojIyMgR2VuZSBuYW1lcyBob21vZ2VuaXphdGlvbgoKVG8gd2hpY2ggZXh0ZW50IGdlbmUgbmFtZXMgYXJlIGNvbW1vbiBiZXR3ZWVuIGFsbCBkYXRhc2V0cyA/CgpgYGB7ciBjb21tb25fZ2VuZXNfdXBzZXQsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOH0KZ2VuZV9uYW1lcyA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IHJvd25hbWVzKQpvYmpfdXBzZXQgPSBDb21wbGV4SGVhdG1hcDo6bWFrZV9jb21iX21hdChnZW5lX25hbWVzLCBtb2RlID0gImRpc3RpbmN0IikKQ29tcGxleEhlYXRtYXA6OlVwU2V0KG9ial91cHNldCkKYGBgCgpGb3Igb3VyIGRhdGEgYW5kIFd1IGRhdGEsIHdlIGhhdmUgdGhlIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gZ2VuZSBuYW1lcyBhbmQgRW5zZW1ibCBJRHMuIEZvciBUYWthaGFzaGkgZGF0YSwgd2UgdHJ5IHRvIGZpbmQgdGhlbSB1c2luZyBgYml0cmAgZnVuY3Rpb24gZnJvbSBgY2x1c3RlclByb2ZpbGVyYCBwYWNrYWdlLgoKCmBgYHtyIGNvcnJlc3BvbmRlbmNlLCB0aW1lX2l0ID0gVFJVRX0KYW5ub3RhdGlvbiA9IGNsdXN0ZXJQcm9maWxlcjo6Yml0cigKICBnZW5lSUQgICA9IHVuaXF1ZSh1bmxpc3QoZ2VuZV9uYW1lcykpLAogIGZyb21UeXBlID0gIlNZTUJPTCIsCiAgdG9UeXBlICAgPSAiRU5TRU1CTCIsCiAgT3JnRGIgICAgPSBvcmcuSHMuZWcuZGIsCiAgZHJvcCA9IEZBTFNFKQoKaGVhZChhbm5vdGF0aW9uKQpgYGAKClRoZXJlIGlzIGEgbG90IG9mIE5BLiBHaXZlbiB0aGF0IG91ciBkYXRhIGFuZCBXdSBkYXRhIHdlcmUgbWFwcGVkIHVzaW5nIHRoZSBzYW1lIGFubm90YXRpb24sIHdlIGNvbXBsZXRlIHRoZSBhbm5vdGF0aW9uIHRhYmxlIHVzaW5nIHRoZSBFbnNlbWJsIElEIHdlIHN0b3JlZCBpbiBlYWNoIGluZGl2aWR1YWwgU2V1cmF0IG9iamVjdHM6CgpgYGB7ciBjb21wbGV0ZV9vdXJ9CmFubm90YXRpb24gPSBkcGx5cjo6bGVmdF9qb2luKAogIHggPSBhbm5vdGF0aW9uLAogIHkgPSBzb2JqX2xpc3RbWyJoZXJlLjIwMjFfMzEiXV1AYXNzYXlzW1siUk5BIl1dQG1ldGEuZmVhdHVyZXNbLCBjKCJFbnNlbWJsX0lEIiwgImdlbmVfbmFtZSIpXSwKICBieSA9IGMoIlNZTUJPTCIgPSAiZ2VuZV9uYW1lIikpCgpoZWFkKGFubm90YXRpb24pCmBgYAoKVGhlcmUgaXMgc3RpbGwgYSBsb3Qgb2YgZGF0YS4gV2UgdHJ5IHRvIGNvbXBsZXRlIHRoZSBkYXRhZnJhbWUgdXNpbmcgdGhlIGBiaW9tYVJ0YCBwYWNrYWdlOgoKYGBge3IgY29tcGxldGVfYmlvbWFydCwgdGltZV9pdCA9IFRSVUV9Cmh0dHI6OnNldF9jb25maWcoaHR0cjo6Y29uZmlnKHNzbF92ZXJpZnlwZWVyID0gRkFMU0UpKQptYXJ0ID0gYmlvbWFSdDo6dXNlRW5zZW1ibChiaW9tYXJ0ID0gImVuc2VtYmwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0ID0gImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIpCmNvcnJlc3AgPSBiaW9tYVJ0OjpnZXRCTShhdHRyaWJ1dGVzID0gYygiZW5zZW1ibF9nZW5lX2lkIiwgImhnbmNfc3ltYm9sIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXJzID0gImhnbmNfc3ltYm9sIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IHVuaXF1ZSh1bmxpc3QoZ2VuZV9uYW1lcykpLAogICAgICAgICAgICAgICAgICAgICAgICAgbWFydCA9IG1hcnQpCgpoZWFkKGNvcnJlc3ApCmBgYAoKV2UgbWVyZ2UgdGhpcyBpbmZvcm1hdGlvbiB3aXRoIHRoZSBgYW5ub3RhdGlvbmAgdGFibGU6CgpgYGB7ciBjb21wbGV0ZV9jb3JyZXNwfQphbm5vdGF0aW9uID0gZHBseXI6OmxlZnRfam9pbigKICB4ID0gYW5ub3RhdGlvbiwKICB5ID0gY29ycmVzcCwKICBieSA9IGMoIlNZTUJPTCIgPSAiaGduY19zeW1ib2wiKSkKCmhlYWQoYW5ub3RhdGlvbikKYGBgCgpXZSBtZXJnZSB0aGUgdHdvIEVuc2VtYmwgY29sdW1uczoKCmBgYHtyIGZpbmFsX2Fubm90YXRpb30KYW5ub3RhdGlvbiA9IGFubm90YXRpb24gJT4lCiAgZHBseXI6Om11dGF0ZShFTlNFTUJMX0lEID0gaWZlbHNlKAogICAgIWlzLm5hKEVOU0VNQkwpLAogICAgeWVzID0gYXMuY2hhcmFjdGVyKEVOU0VNQkwpLAogICAgbm8gPSBpZmVsc2UoIWlzLm5hKGVuc2VtYmxfZ2VuZV9pZCksCiAgICAgICAgICAgICAgICB5ZXMgPSBhcy5jaGFyYWN0ZXIoZW5zZW1ibF9nZW5lX2lkKSwKICAgICAgICAgICAgICAgIG5vID0gYXMuY2hhcmFjdGVyKEVuc2VtYmxfSUQpKSkpICU+JQogIGRwbHlyOjpzZWxlY3QoU1lNQk9MLCBFTlNFTUJMX0lEKSAlPiUKICB1bmlxdWUoKQoKaGVhZChhbm5vdGF0aW9uKQpgYGAKCkhvdyBtYW55IE5BIGRhdGEgYXJlIHJlbWFpbmluZyA/CgpgYGB7ciB0YWJsZV9uYX0KdGFibGUoaXMubmEoYW5ub3RhdGlvbiRFTlNFTUJMX0lEKSkKYGBgCgpGb3Igd2hpY2ggZGF0YXNldCBFbnNlbWJsIElEcyBhcmUgbm90IGF2YWlsYWJsZSA/CgpgYGB7ciBnZW5lc193aXRob3V0X2Vuc2VtYmx9CmdlbmVzX3dpdGhvdXRfZW5zZW1ibCA9IGFubm90YXRpb24gJT4lCiAgZHBseXI6OmZpbHRlcihpcy5uYShFTlNFTUJMX0lEKSkgJT4lCiAgZHBseXI6OnB1bGwoU1lNQk9MKQoKbGFwcGx5KGdlbmVfbmFtZXMsIEZVTiA9IGZ1bmN0aW9uKHNhbXBsZV9nZW5lcykgewogIHJldHVybih0YWJsZShzYW1wbGVfZ2VuZXMgJWluJSBnZW5lc193aXRob3V0X2Vuc2VtYmwpKQp9KSAlPiUgZG8uY2FsbChyYmluZCwgLikgJT4lCiAgYGNvbG5hbWVzPC1gKGMoIklEIGF2YWlsYWJsZSIsICJJRCBub3QgYXZhaWxhYmxlIikpCmBgYAoKVGhlIGZhY3QgdGhhdCAyMCBJRCBhcmUgbm90IGF2YWlsYWJsZSBpbiBvdXIgZGF0YSBhbmQgV3UgZGF0YSBpcyBkdWUgdG8gZ2VuZXMgc2hhcmluZyB0aGUgc2FtZSBFbnNlbWJsIElELi4uIFdlIHdpbGwgc3RpbGwga2VlcCBhbGwgdGhlIGdlbmVzIGF2YWlsYWJsZSBpbiB0aGVzZSB0d28gc2V0cyBvZiBkYXRhLiBUaGUgZ29hbCBoZXJlIGlzIHRvIGZpbmQgYSBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGdlbmUgbmFtZXMgZnJvbSBUYWthaGFzaGkgZGF0YSBhbmQgZnJvbSBvdXIgYW5kIFd1IGRhdGEsIHBhc3NpbmcgYnkgRW5zZW1ibCBJRHMuCgpTb21lIGdlbmVzIHNoYXJlIHRoZSBzYW1lIHN5bWJvbCBidXQgbm90IHRoZSBFbnNlbWJsIElELiBTaW5jZSBpbiBUYWthaGFzaGkgZGF0YSwgdGhlcmUgYXJlIG9ubHkgc3ltYm9scywgd2UgY2FuIG5vdCBrbm93IHRoZSBFbnNlbWJsIElEIHRvIHdoaWNoIHRoZXkgY29ycmVzcG9uZC4gKipXZSBtYWtlIGEgY2hvaWNlIHRvIHNlbGVjdCB0aGUgZmlyc3QgRW5zZW1ibCBJRCBhdmFpbGFibGUgZm9yIHRoZSBzeW1ib2wuKiogRm9yIHRoaXMsIHdlIHNlbGVjdCBvbmx5IHRoZSB1bmlxdWUgc3ltYm9sIGluIHRoZSBgYW5ub3RhdGlvbmAgdGFibGUuCgpgYGB7ciBkZWR1cGxfYW5ub3RhdGlvbn0KdGFibGUoZHVwbGljYXRlZChhbm5vdGF0aW9uJFNZTUJPTCkpCmRpbShhbm5vdGF0aW9uKQoKYW5ub3RhdGlvbiA9IGFubm90YXRpb24gJT4lCiAgZHBseXI6OmZpbHRlcighZHVwbGljYXRlZChTWU1CT0wpKQoKZGltKGFubm90YXRpb24pCmBgYAoKTm93LCB3ZSBjYW4gc2V0IHN5bWJvbCBhcyByb3cgbmFtZXM6CgpgYGB7ciByb3duYW1lc19hbm5vdH0Kcm93bmFtZXMoYW5ub3RhdGlvbikgPSBhbm5vdGF0aW9uJFNZTUJPTAoKaGVhZChhbm5vdGF0aW9uKQpgYGAKCiMjIyBFeHRyYWN0IGNvdW50IG1hdHJpY2VzCgpGb3IgZWFjaCBkYXRhc2V0LCB3ZSBleHRyYWN0IHRoZSBjb3VudCBtYXRyaWNlcyBhbmQgbWV0YWRhdGEuIFRoZSBnZW5lIG5hbWVzIGluIHRoZSBjb3VudCBtYXRyaWNlcyB3aWxsIGJlIGNvbnZlcnQgdG8gRW5zZW1ibCBJRCwgdXNpbmcgb3VyIGNvcnJlc3BvbmRlbmNlIG9mIFd1IGFuZCBvdXIgZGF0YSwgb3IgdGhlIGBhbm5vdGF0aW9uYCB0YWJsZSBmb3IgVGFrYWhhc2hpIGRhdGEuCgpgYGB7ciBjb3VudF9tYXRyaXhfbGlzdH0Kb3VyX2NvcnJlc3BvbmRlbmNlID0gc29ial9saXN0W1siaGVyZS4yMDIxXzMxIl1dQGFzc2F5c1tbIlJOQSJdXUBtZXRhLmZlYXR1cmVzWywgYygiRW5zZW1ibF9JRCIsICJnZW5lX25hbWUiKV0Kb3VyX2NvcnJlc3BvbmRlbmNlJG5vX2R1cF9nZW5lX25hbWVzID0gcm93bmFtZXMob3VyX2NvcnJlc3BvbmRlbmNlKQpyb3duYW1lcyhvdXJfY29ycmVzcG9uZGVuY2UpID0gb3VyX2NvcnJlc3BvbmRlbmNlJEVuc2VtYmxfSUQKCmNvdW50X21hdHJpeF9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICBwcm9qZWN0X25hbWUgPSB1bmlxdWUob25lX3NvYmokcHJvamVjdF9uYW1lKQogIGNvdW50X21hdHJpeCA9IG9uZV9zb2JqQGFzc2F5c1tbIlJOQSJdXUBjb3VudHMKICAKICBpZiAocHJvamVjdF9uYW1lICVpbiUgc2FtcGxlX2luZm9fMyRwcm9qZWN0X25hbWUpIHsKICAgICMgZ2V0IEVuc2VtYmwgSUQgY29ycmVzcG9uZGluZyB0byBnZW5lIG5hbWVzCiAgICB0aGlzX2RhdGFzZXRfYW5ub3QgPSBhbm5vdGF0aW9uW3Jvd25hbWVzKGNvdW50X21hdHJpeCksIF0KICAgIAogICAgIyBnZXQgZ2VuZSBuYW1lcyBjb3JyZXNwb25kaW5nIHRvIEVuc2VtYmwgSUQsIHVzaW5nIG91ciBjb3JyZXNwb25kZW5jZSAoc2FtZSBkaW0pCiAgICB0aGlzX2RhdGFzZXRfYW5ub3QgPSBkcGx5cjo6bGVmdF9qb2luKHggPSB0aGlzX2RhdGFzZXRfYW5ub3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBvdXJfY29ycmVzcG9uZGVuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiRU5TRU1CTF9JRCIgPSAiRW5zZW1ibF9JRCIpKQogICAgCiAgICAjIHJlIHNldCBvbGQgZ2VuZSBuYW1lcyBpZiBub25lIGFyZSBhdmFpbGFibGUgdXNpbmcgb3VyIGNvcnJlc3BvbmRlbmNlCiAgICB0aGlzX2RhdGFzZXRfYW5ub3QgPSB0aGlzX2RhdGFzZXRfYW5ub3QgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUobm9fZHVwX2dlbmVfbmFtZXMgPSBpZmVsc2UoaXMubmEobm9fZHVwX2dlbmVfbmFtZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllcyA9IFNZTUJPTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubyA9IG5vX2R1cF9nZW5lX25hbWVzKSkKICAgIHJvd25hbWVzKHRoaXNfZGF0YXNldF9hbm5vdCkgPSB0aGlzX2RhdGFzZXRfYW5ub3QkU1lNQk9MICMgYWxsIGluIHRoZSBjb3VudCBtYXRyaWNlcwogICAgCiAgICAjIGNvbnZlcnQgdGhlIHJvdyBuYW1lcyBvZiB0aGUgY291bnQgbWF0cml4CiAgICByb3duYW1lcyhjb3VudF9tYXRyaXgpID0gdGhpc19kYXRhc2V0X2Fubm90W3Jvd25hbWVzKGNvdW50X21hdHJpeCksICJub19kdXBfZ2VuZV9uYW1lcyJdCiAgfQogIAogIHJldHVybihjb3VudF9tYXRyaXgpCn0pCgpsYXBwbHkoY291bnRfbWF0cml4X2xpc3QsIEZVTiA9IGRpbSkgJT4lCiAgZG8uY2FsbChyYmluZCwgLikgJT4lCiAgYGNvbG5hbWVzPC1gKGMoIk5iIGdlbmVzIiwgIk5iIGNlbGxzIikpCmBgYAoKV2hhdCBpcyB0aGUgYXNwZWN0IG9mIHRoZSBuZXcgdXBzZXQgcGxvdCA/CgpgYGB7ciBjb21tb25fZ2VuZXNfdXBzZXQyLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDh9CmdlbmVfbmFtZXMgPSBsYXBwbHkoY291bnRfbWF0cml4X2xpc3QsIEZVTiA9IHJvd25hbWVzKQpvYmpfdXBzZXQgPSBDb21wbGV4SGVhdG1hcDo6bWFrZV9jb21iX21hdChnZW5lX25hbWVzLCBtb2RlID0gImRpc3RpbmN0IikKQ29tcGxleEhlYXRtYXA6OlVwU2V0KG9ial91cHNldCkKYGBgCgpJdCBhbG1vc3QgZGlkIG5vdCBjaGFuZ2UgYW55dGhpbmcuLi4gSW5kZWVkOgoKYGBge3IgY2hlY2tfc2FkbmVzc30KdGFibGUocm93bmFtZXMoc29ial9saXN0JHRha2FoYXNoaS5HU00zNzE3MDM3KSAlaW4lIHJvd25hbWVzKGNvdW50X21hdHJpeF9saXN0JHRha2FoYXNoaS5HU00zNzE3MDM3KSkKYGBgCgpTbyBmb3IgdGhlIGZpbmFsIGRhdGFzZXQsIHdlIHdpbGwgZXh0cmFjdCBvbmx5IHRoZSBjb21tb24gZ2VuZXMgYmV0d2VlbiBhbGwgZGF0YXNldHMuIEl0IGNvcnJlc3BvbmRzIHRvIHRoZSBmaXJzdCBjb2x1bW4gaW4gdG5lIHVwc2V0IHBsb3QuCgpgYGB7ciBjb21tb25fZ2VuZXN9CmNvbW1vbl9nZW5lcyA9IFJlZHVjZSh4ID0gbGFwcGx5KGNvdW50X21hdHJpeF9saXN0LCBGVU4gPSByb3duYW1lcyksCiAgICAgICAgICAgICAgICAgICAgICBmID0gaW50ZXJzZWN0KQoKbGVuZ3RoKGNvbW1vbl9nZW5lcykKYGBgCgpXZSBzdWJzZXQgYWxsIGRhdGFzZXRzIHRvIGtlZXAgb25seSB0aGVzZSBnZW5lcyA6CgpgYGB7ciBzdWJzZXRfb2JqZWN0fQpjb3VudF9tYXRyaXhfbGlzdCA9IGxhcHBseShjb3VudF9tYXRyaXhfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX2NvdW50X21hdHJpeCkgewogIG9uZV9jb3VudF9tYXRyaXggPSBvbmVfY291bnRfbWF0cml4W2NvbW1vbl9nZW5lcywgXQogIAogIHJldHVybihvbmVfY291bnRfbWF0cml4KQp9KQoKbGFwcGx5KGNvdW50X21hdHJpeF9saXN0LCBGVU4gPSBkaW0pICU+JQogIGRvLmNhbGwocmJpbmQsIC4pICU+JQogIGBjb2xuYW1lczwtYChjKCJOYiBnZW5lcyIsICJOYiBjZWxscyIpKQpgYGAKClRvIG1lcmdlIGFsbCB0aGUgY291bnQgbWF0cmljZXMsIHdlIGFkZCBhIHByZWZpeCB0byBlYWNoIGNlbGwgdG8gYXZvaWQgZHVwbGljYXRlZCBjZWxsIGJhcmNvZGVzLiBUaGUgcHJlZml4IGlzOgoKYGBge3IgcHJlZml4fQpuYW1lcyhjb3VudF9tYXRyaXhfbGlzdCkgPSBuYW1lcyhjb3VudF9tYXRyaXhfbGlzdCkgJT4lCiAgc3RyaW5ncjo6c3RyX3NwbGl0KHN0cmluZyA9IC4sCiAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiXFwuIikgJT4lCiAgbGFwcGx5KC4sIGBbW2AsIDIpICU+JQogIHVubGlzdCgpCm5hbWVzKHNvYmpfbGlzdCkgPSBuYW1lcyhjb3VudF9tYXRyaXhfbGlzdCkKCm5hbWVzKGNvdW50X21hdHJpeF9saXN0KQpgYGAKCldlIGFkZCB0aGUgcHJlZml4IGZvciBlYWNoIGNvdW50IG1hdHJpeCBhbmQgbWVyZ2UgdGhlbToKCmBgYHtyIGNvdW50X21hdHJpeF9tZXJnZX0KY291bnRfbWF0cml4X21lcmdlID0gbGFwcGx5KG5hbWVzKGNvdW50X21hdHJpeF9saXN0KSwgRlVOID0gZnVuY3Rpb24ob25lX3Byb2plY3RfbmFtZSkgewogIG9uZV9jb3VudF9tYXRyaXggPSBjb3VudF9tYXRyaXhfbGlzdFtbb25lX3Byb2plY3RfbmFtZV1dCiAgY29sbmFtZXMob25lX2NvdW50X21hdHJpeCkgPSBwYXN0ZTAob25lX3Byb2plY3RfbmFtZSwgIl8iLCBjb2xuYW1lcyhvbmVfY291bnRfbWF0cml4KSkKCiAgcmV0dXJuKG9uZV9jb3VudF9tYXRyaXgpCn0pICU+JSBkby5jYWxsKGNiaW5kLmRhdGEuZnJhbWUsIC4pCgpkaW0oY291bnRfbWF0cml4X21lcmdlKQpjb3VudF9tYXRyaXhfbWVyZ2VbYygxOjMpLCBjKDE6MyldCmBgYAoKIyMjIEV4dHJhY3QgbWV0YWRhdGEKClNpbWlsYXJseSwgd2UgYnVpbGQgYSBiaWcgbWV0YWRhdGEgdGFibGU6CgpgYGB7ciBtYWtlX2JpZ19tZXRhZGF0YX0KY29sdW1uc19vZl9pbnRlcmVzdCA9IGMoIm9yaWcuaWRlbnQiLCAibkNvdW50X1JOQSIsICJuRmVhdHVyZV9STkEiLAogICAgICAgICAgICAgICAgICAgICAgICAibG9nX25Db3VudF9STkEiLCAicHJvamVjdF9uYW1lIiwgInNhbXBsZV9pZGVudGlmaWVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgInNhbXBsZV90eXBlIiwgIlNldXJhdC5QaGFzZSIsICJjeWNsb25lLlBoYXNlIiwKICAgICAgICAgICAgICAgICAgICAgICAgInBlcmNlbnQubXQiLCAicGVyY2VudC5yYiIsICJjZWxsX3R5cGUiICkKCm1ldGFkYXRhX21lcmdlID0gbGFwcGx5KG5hbWVzKHNvYmpfbGlzdCksIEZVTiA9IGZ1bmN0aW9uKG9uZV9wcm9qZWN0X25hbWUpIHsKICBvbmVfc29iaiA9IHNvYmpfbGlzdFtbb25lX3Byb2plY3RfbmFtZV1dCiAgbWV0YWRhdGEgPSBvbmVfc29iakBtZXRhLmRhdGEKICByb3duYW1lcyhtZXRhZGF0YSkgPSBwYXN0ZTAob25lX3Byb2plY3RfbmFtZSwgIl8iLCByb3duYW1lcyhtZXRhZGF0YSkpCiAgbWV0YWRhdGEgPSBtZXRhZGF0YVssIGNvbHVtbnNfb2ZfaW50ZXJlc3RdCiAgCiAgcmV0dXJuKG1ldGFkYXRhKQp9KSAlPiUgZG8uY2FsbChyYmluZC5kYXRhLmZyYW1lLCAuKQoKZGltKG1ldGFkYXRhX21lcmdlKQpoZWFkKG1ldGFkYXRhX21lcmdlKQpgYGAKCiMjIyBDb21iaW5lZCBkYXRhc2V0CgpXZSBnZW5lcmF0ZSBhIG5ldyBTZXVyYXQgb2JqZWN0IGZyb20gdGhlIGNvdW50IG1hdHJpeCBhbmQgdGhlIG1ldGFkYXRhOgoKYGBge3IgY29tYmluZWRfZGF0YXNldH0KZGltKGNvdW50X21hdHJpeF9tZXJnZSkKZGltKG1ldGFkYXRhX21lcmdlKQphbGwuZXF1YWwocm93bmFtZXMobWV0YWRhdGFfbWVyZ2UpLCBjb2xuYW1lcyhjb3VudF9tYXRyaXhfbWVyZ2UpKQoKc29iaiA9IFNldXJhdDo6Q3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGNvdW50X21hdHJpeF9tZXJnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEuZGF0YSA9IG1ldGFkYXRhX21lcmdlKQpzb2JqCmBgYAoKV2UgYWRkIHRoZSBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGdlbmUgbmFtZXMgYW5kIGdlbmUgSUQuCgpgYGB7ciBhZGRfbWV0YWZlYXR1cmVzfQpzb2JqQGFzc2F5cyRSTkFAbWV0YS5mZWF0dXJlcyA9IGFubm90YXRpb25bY29tbW9uX2dlbmVzLCBdCgpoZWFkKHNvYmpAYXNzYXlzJFJOQUBtZXRhLmZlYXR1cmVzKQpgYGAKCldlIHJlbW92ZSB3aGF0IHdlIHdpbGwgbm90IHVzZSA6CgpgYGB7ciBjbGVhbl9zb2JqX2xpc3R9CnJtKHNvYmpfbGlzdCwgYW5ub3RhdGlvbiwgY29ycmVzcCwgb3VyX2NvcnJlc3BvbmRlbmNlLCBvYmpfdXBzZXQsCiAgIGdlbmVfbmFtZXMsIG1hcnQsIHBsb3RfbGlzdCwKICAgc2FtcGxlX2luZm9fMSwgc2FtcGxlX2luZm9fMiwgc2FtcGxlX2luZm9fMywgY29sdW1uX3RvX2tlZXAsCiAgIGNvdW50X21hdHJpeF9tZXJnZSwgY291bnRfbWF0cml4X2xpc3QsIG1ldGFkYXRhX21lcmdlLAogICBjb2x1bW5fdG9fa2VlcCwgY29tbW9uX2dlbmVzLCBjb2x1bW5zX29mX2ludGVyZXN0KQpgYGAKCldlIGtlZXAgYSBzdWJzZXQgb2YgbWV0YS5kYXRhIGFuZCByZXNldCBsZXZlbHMgOgoKYGBge3Igc29ial9zZXRfZmFjdG9yX2xldmVsc30Kc29iaiRwcm9qZWN0X25hbWUgPSBmYWN0b3Ioc29iaiRwcm9qZWN0X25hbWUsIGxldmVscyA9IGxldmVscyhzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpKQoKc3VtbWFyeShzb2JqQG1ldGEuZGF0YSkKYGBgCgojIFByb2Nlc3NpbmcKCldlIHJlbW92ZSBnZW5lcyB0aGF0IGFyZSBleHByZXNzZWQgaW4gbGVzcyB0aGFuIDUgY2VsbHMgOgoKYGBge3IgZmlsdGVyX2dlbmVzfQpzb2JqID0gYXF1YXJpdXM6OmZpbHRlcl9mZWF0dXJlcyhzb2JqLCBtaW5fY2VsbHMgPSA1KQpzb2JqCmBgYAoKV2UgcmVtb3ZlIGNlbGxzIGV4cHJlc3NpbmcgbGVzcyB0aGFuIDIwMCBnZW5lcy4KCmBgYHtyIGZpbHRlcl9jZWxsc30Kc29iaiA9IHN1YnNldChzb2JqLCBuRmVhdHVyZV9STkEgPiAyNTApCnNvYmoKYGBgCgoKIyMgTWV0YWRhdGEKCkhvdyBtYW55IGNlbGxzIGJ5IHNhbXBsZSA/CgpgYGB7ciB0YWJsZV9vcmlnX2lkZW50fQp0YWJsZShzb2JqJHByb2plY3RfbmFtZSkKYGBgCgpXZSByZXByZXNlbnQgdGhpcyBpbmZvcm1hdGlvbiBhcyBhIGJhcnBsb3QgOgoKYGBge3IgYmFycGxvdF9jb3VudCwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQphcXVhcml1czo6cGxvdF9iYXJwbG90KGRmID0gdGFibGUoc29iaiRwcm9qZWN0X25hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb2JqJGNlbGxfdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lLnRhYmxlKCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBgY29sbmFtZXM8LWAoYygiU2FtcGxlIiwgIkNlbGwgVHlwZSIsICJOdW1iZXIiKSksCiAgICAgICAgICAgICAgICAgICAgICAgeCA9ICJTYW1wbGUiLCB5ID0gIk51bWJlciIsIGZpbGwgPSAiQ2VsbCBUeXBlIiwKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ZpbGwoKSkgKwogIGdncGxvdDI6OnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHVubGlzdChjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkNlbGwgVHlwZSIpCmBgYAoKVGhpcyBpcyB0aGUgc2FtZSBiYXJwbG90IHdpdGggYW5vdGhlciBwb3NpdGlvbiA6CgpgYGB7ciBiYXJwbG90X3N0YWNrLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9CmFxdWFyaXVzOjpwbG90X2JhcnBsb3QoZGYgPSB0YWJsZShzb2JqJHByb2plY3RfbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvYmokY2VsbF90eXBlKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIGBjb2xuYW1lczwtYChjKCJTYW1wbGUiLCAiQ2VsbCBUeXBlIiwgIk51bWJlciIpKSwKICAgICAgICAgICAgICAgICAgICAgICB4ID0gIlNhbXBsZSIsIHkgPSAiTnVtYmVyIiwgZmlsbCA9ICJDZWxsIFR5cGUiLAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2soKSkgKwogIGdncGxvdDI6OnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHVubGlzdChjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkNlbGwgVHlwZSIpCmBgYAoKIyMgUHJvamVjdGlvbgoKV2Ugbm9ybWFsaXplIHRoZSBjb3VudCBtYXRyaXggZm9yIHJlbWFpbmluZyBjZWxscyBhbmQgc2VsZWN0IGhpZ2hseSB2YXJpYWJsZSBmZWF0dXJlcyA6CgpgYGB7ciBub3JtYWxpemF0aW9ufQpzb2JqID0gU2V1cmF0OjpOb3JtYWxpemVEYXRhKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiTG9nTm9ybWFsaXplIikKc29iaiA9IFNldXJhdDo6RmluZFZhcmlhYmxlRmVhdHVyZXMoc29iaiwgbmZlYXR1cmVzID0gMjAwMCkKc29iaiA9IFNldXJhdDo6U2NhbGVEYXRhKHNvYmopCgpzb2JqCmBgYAoKV2UgcGVyZm9ybSBhIFBDQSA6CgpgYGB7ciBwY2F9CnNvYmogPSBTZXVyYXQ6OlJ1blBDQShzb2JqLAogICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiUk5BIiwKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gIlJOQV9wY2EiLAogICAgICAgICAgICAgICAgICAgICAgbnBjcyA9IDEwMCwKICAgICAgICAgICAgICAgICAgICAgIHNlZWQudXNlID0gMTMzN0wpCnNvYmoKYGBgCgpXZSBjaG9vc2UgdGhlIG51bWJlciBvZiBkaW1lbnNpb25zIHN1Y2ggdGhhdCB0aGV5IHN1bW1hcml6ZSA2MCAlIG9mIHRoZSB2YXJpYWJpbGl0eSA6CgpgYGB7ciBuZGltc30Kc3RkZXYgPSBzb2JqQHJlZHVjdGlvbnNbWyJSTkFfcGNhIl1dQHN0ZGV2CnN0ZGV2X3Byb3AgPSBjdW1zdW0oc3RkZXYpL3N1bShzdGRldikKbmRpbXMgPSB3aGljaChzdGRldl9wcm9wID4gMC42MClbMV0KbmRpbXMKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHRoaXMgb24gdGhlIGVsYm93IHBsb3QgOgoKYGBge3IgZWxib3dwbG90LCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDR9CmVsYm93X3AgPSBTZXVyYXQ6OkVsYm93UGxvdChzb2JqLCBuZGltcyA9IDEwMCwgcmVkdWN0aW9uID0gIlJOQV9wY2EiKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludCh4ID0gbmRpbXMsIHkgPSBzdGRldltuZGltc10sIGNvbCA9ICJyZWQiKQp4X3RleHQgPSBnZ3Bsb3RfYnVpbGQoZWxib3dfcCkkbGF5b3V0JHBhbmVsX3BhcmFtc1tbMV1dJHgkZ2V0X2xhYmVscygpICU+JSBhcy5udW1lcmljKCkKZWxib3dfcCA9IGVsYm93X3AgKwogIGdncGxvdDI6OnNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzb3J0KGMoeF90ZXh0LCBuZGltcykpLCBsaW1pdHMgPSBjKDAsIDEwMCkpCnhfY29sb3IgPSBpZmVsc2UoZ2dwbG90X2J1aWxkKGVsYm93X3ApJGxheW91dCRwYW5lbF9wYXJhbXNbWzFdXSR4JGdldF9sYWJlbHMoKSAlPiUKICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoKSAlPiUgcm91bmQoLiwgMikgPT0gcm91bmQobmRpbXMsIDIpLCAicmVkIiwgImJsYWNrIikKZWxib3dfcCA9IGVsYm93X3AgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSB4X2NvbG9yKSkKCmVsYm93X3AKYGBgCgpXZSBnZW5lcmF0ZSBhIHRTTkUgYW5kIGEgVU1BUCB3aXRoIGByIG5kaW1zYCBwcmluY2lwYWwgY29tcG9uZW50cyA6CgpgYGB7ciB0c25lX3VtYXAsIHRpbWVfaXQgPSBUUlVFfQpzb2JqID0gU2V1cmF0OjpSdW5UU05FKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gIlJOQV9wY2EiLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXMgPSAxOm5kaW1zLAogICAgICAgICAgICAgICAgICAgICAgIHNlZWQudXNlID0gMTMzN0wsCiAgICAgICAgICAgICAgICAgICAgICAgbnVtX3RocmVhZHMgPSBuX3RocmVhZHMsICMgUnRzbmU6OlJ0c25lIG9wdGlvbgogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gcGFzdGUwKCJSTkFfcGNhXyIsIG5kaW1zLCAiX3RzbmUiKSkKCnNvYmogPSBTZXVyYXQ6OlJ1blVNQVAoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiUk5BX3BjYSIsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IDE6bmRpbXMsCiAgICAgICAgICAgICAgICAgICAgICAgc2VlZC51c2UgPSAxMzM3TCwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24ubmFtZSA9IHBhc3RlMCgiUk5BX3BjYV8iLCBuZGltcywgIl91bWFwIikpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSB0aGUgdHdvIHJlcHJlc2VudGF0aW9ucyA6CgpgYGB7ciBzZWVfdW1hcF90c25lLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnRzbmUgPSBTZXVyYXQ6OkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAicHJvamVjdF9uYW1lIiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBwYXN0ZTAoIlJOQV9wY2FfIiwgbmRpbXMsICJfdHNuZSIpKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZSgiUENBIC0gdFNORSIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnVtYXAgPSBTZXVyYXQ6OkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAicHJvamVjdF9uYW1lIiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBwYXN0ZTAoIlJOQV9wY2FfIiwgbmRpbXMsICJfdW1hcCIpKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZSgiUENBIC0gVU1BUCIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKdHNuZSB8IHVtYXAKYGBgCgojIyBCYXRjaC1lZmZlY3QgY29ycmVjdGlvbgoKV2UgcmVtb3ZlIHNhbXBsZSBzcGVjaWZpYyBlZmZlY3Qgb24gdGhlIHBjYSB1c2luZyBIYXJtb255IDoKCmBgYHtyIGhhcm1vbnksIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1LCB0aW1lX2l0ID0gVFJVRX0KYCV8fCVgID0gZnVuY3Rpb24obGhzLCByaHMpIHsKICBpZiAoIWlzLm51bGwoeCA9IGxocykpIHsKICAgIHJldHVybihsaHMpCiAgfSBlbHNlIHsKICAgIHJldHVybihyaHMpCiAgfQp9CgpzZXQuc2VlZCgxMzM3TCkKc29iaiA9IGhhcm1vbnk6OlJ1bkhhcm1vbnkob2JqZWN0ID0gc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkudmFycyA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X2NvbnZlcmdlbmNlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gIlJOQV9wY2EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheS51c2UgPSAiUk5BIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLnNhdmUgPSAiaGFybW9ueSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5pdGVyLmhhcm1vbnkgPSA1MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvamVjdC5kaW0gPSBGQUxTRSkKYGBgCgpGcm9tIHRoaXMgYmF0Y2gtZWZmZWN0IHJlbW92ZWQgcHJvamVjdGlvbiwgd2UgZ2VuZXJhdGUgYSB0U05FIGFuZCBhIFVNQVAuCgpgYGB7ciBoYXJtb255X3RzbmVfdW1hcCwgdGltZV9pdCA9IFRSVUV9CnNvYmogPSBTZXVyYXQ6OlJ1blVNQVAoc29iaiwgCiAgICAgICAgICAgICAgICAgICAgICAgc2VlZC51c2UgPSAxMzM3TCwKICAgICAgICAgICAgICAgICAgICAgICBkaW1zID0gMTpuZGltcywKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiaGFybW9ueSIsCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLm5hbWUgPSBwYXN0ZTAoImhhcm1vbnlfIiwgbmRpbXMsICJfdW1hcCIpLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5rZXkgPSBwYXN0ZTAoImhhcm1vbnlfIiwgbmRpbXMsICJ1bWFwXyIpKQoKc29iaiA9IFNldXJhdDo6UnVuVFNORShzb2JqLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXMgPSAxOm5kaW1zLAogICAgICAgICAgICAgICAgICAgICAgIHNlZWQudXNlID0gMTMzN0wsCiAgICAgICAgICAgICAgICAgICAgICAgbnVtX3RocmVhZHMgPSBuX3RocmVhZHMsICMgUnRzbmU6OlJ0c25lIG9wdGlvbgogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJoYXJtb255IiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24ubmFtZSA9IHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl90c25lIiksCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmtleSA9IHBhc3RlMCgiaGFybW9ueSIsIG5kaW1zLCAidHNuZV8iKSkKYGBgCgpXZSB2aXN1YWxpemUgdGhlIGNvcnJlY3RlZCBwcm9qZWN0aW9ucyA6CgpgYGB7ciBzZWVfdW1hcF90c25lX2FmdGVyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnRzbmUgPSBTZXVyYXQ6OkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAicHJvamVjdF9uYW1lIiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBwYXN0ZTAoImhhcm1vbnlfIiwgbmRpbXMsICJfdHNuZSIpKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZSgiUENBIC0gaGFybW9ueSAtIHRTTkUiKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgp1bWFwID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gcGFzdGUwKCJoYXJtb255XyIsIG5kaW1zLCAiX3VtYXAiKSkgKwogIGdncGxvdDI6OnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArIGdncGxvdDI6OmdndGl0bGUoIlBDQSAtIGhhcm1vbnkgLSBVTUFQIikgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgp0c25lIHwgdW1hcApgYGAKCldlIHdpbGwga2VlcCB0aGUgdFNORSBmcm9tIGhhcm1vbnkgOgoKYGBge3Igc2V0X25hbWUyRH0KcmVkdWN0aW9uID0gImhhcm1vbnkiCm5hbWUyRCA9IHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl90c25lIikKYGBgCgoKIyMgQ2x1c3RlcmluZwoKV2UgZ2VuZXJhdGUgYSBjbHVzdGVyaW5nIDoKCmBgYHtyIGNsdXN0ZXJpbmcsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSA2fQpzb2JqID0gU2V1cmF0OjpGaW5kTmVpZ2hib3JzKHNvYmosIHJlZHVjdGlvbiA9IHJlZHVjdGlvbiwgZGltcyA9IDE6bmRpbXMpCnNvYmogPSBTZXVyYXQ6OkZpbmRDbHVzdGVycyhzb2JqLCByZXNvbHV0aW9uID0gMS41KQoKY2x1c3RlcnNfcGxvdCA9IFNldXJhdDo6RGltUGxvdChzb2JqLCByZWR1Y3Rpb24gPSBuYW1lMkQsIGxhYmVsID0gVFJVRSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBTZXVyYXQ6Ok5vTGVnZW5kKCkgKwogIGdncGxvdDI6OmxhYnModGl0bGUgPSAiQ2x1c3RlcnMgSUQiKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKY2x1c3RlcnNfcGxvdApgYGAKCgojIFZpc3VhbGl6YXRpb24KCldlIHJlcHJlc2VudCB0aGUgNCBxdWFsaXR5IG1ldHJpY3MgOgoKYGBge3IgcWNfcGxvdCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSAzfQpwbG90X2xpc3QgPSBTZXVyYXQ6OkZlYXR1cmVQbG90KHNvYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lID0gRkFMU0UsIHB0LnNpemUgPSAwLjI1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gYygicGVyY2VudC5tdCIsICJwZXJjZW50LnJiIiwgImxvZ19uQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIpKQpwbG90X2xpc3QgPSBsYXBwbHkocGxvdF9saXN0LCBGVU4gPSBmdW5jdGlvbihvbmVfcGxvdCkgewogIG9uZV9wbG90ICsKICAgIFNldXJhdDo6Tm9BeGVzKCkgKwogICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGFxdWFyaXVzOjo6Y29sb3JfZ2VuZSkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSkKfSkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5yb3cgPSAxKQpgYGAKCgojIyBDbHVzdGVycwoKV2UgY2FuIHJlcHJlc2VudCBjbHVzdGVycywgc3BsaXQgYnkgc2FtcGxlIG9mIG9yaWdpbiA6CgpgYGB7ciBwbG90X3NwbGl0X2RpbXJlZF9jbHVzdGVyLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDIwfQpwbG90X2xpc3QgPSBhcXVhcml1czo6cGxvdF9zcGxpdF9kaW1yZWQoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0X2J5ID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRfY29sb3IgPSBzZXROYW1lcyhzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm0gPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfY29sb3IgPSBhcXVhcml1czo6Z2dfY29sb3JfaHVlKGxlbmd0aChsZXZlbHMoc29iaiRzZXVyYXRfY2x1c3RlcnMpKSkpCgpwbG90X2xpc3RbW2xlbmd0aChwbG90X2xpc3QpICsgMV1dID0gY2x1c3RlcnNfcGxvdCArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDbHVzdGVyIElEIikgJgogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxNSkpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gNCkgJgogIFNldXJhdDo6Tm9MZWdlbmQoKQpgYGAKCiMjIENlbGwgdHlwZQoKV2UgdmlzdWFsaXplIGNlbGwgdHlwZSA6CgpgYGB7ciBzZWVfY2VsbF90eXBlLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDh9CnBsb3RfbGlzdCA9IGxhcHBseSgoYyhwYXN0ZTAoIlJOQV9wY2FfIiwgbmRpbXMsICJfdHNuZSIpLAogICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJSTkFfcGNhXyIsIG5kaW1zLCAiX3VtYXAiKSwKICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl90c25lIiksCiAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoImhhcm1vbnlfIiwgbmRpbXMsICJfdW1hcCIpKSksCiAgICAgICAgICAgICAgICAgICBGVU4gPSBmdW5jdGlvbihvbmVfcmVkKSB7CiAgICAgICAgICAgICAgICAgICAgIFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gb25lX3JlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBjb2xvcl9tYXJrZXJzKSArCiAgICAgICAgICAgICAgICAgICAgICAgU2V1cmF0OjpOb0F4ZXMoKSArIGdncGxvdDI6OmdndGl0bGUob25lX3JlZCkgKwogICAgICAgICAgICAgICAgICAgICAgIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgICAgICAgICAgICAgICAgICB9KQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbnJvdyA9IDIpICsKICBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKYGBgCgoKV2UgbWFrZSBhIHJlcHJlc2VudGF0aW9uIHNwbGl0IGJ5IG9yaWdpbiB0byBzaG93IGNlbGwgdHlwZXMgOgoKYGBge3IgY2VsbF90eXBlX3NwbGl0LCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDIwfQpwbG90X2xpc3QgPSBhcXVhcml1czo6cGxvdF9zcGxpdF9kaW1yZWQoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0X2J5ID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdF9jb2xvciA9IHNldE5hbWVzKHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubSA9IHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfY29sb3IgPSBjb2xvcl9tYXJrZXJzKQoKcGxvdF9saXN0W1tsZW5ndGgocGxvdF9saXN0KSArIDFdXSA9IHBhdGNod29yazo6Z3VpZGVfYXJlYSgpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gNCkgKwogIHBhdGNod29yazo6cGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgojIyBDZWxsIGN5Y2xlCgpXZSB2aXN1YWxpemUgY2VsbCBjeWNsZSBhbm5vdGF0aW9uLCBhbmQgQklSQzUgYW5kIFRPUDJBIGV4cHJlc3Npb24gbGV2ZWxzICA6CgpgYGB7ciBjZWxsX2N5Y2xlLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDgsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwbG90X2xpc3QgPSBsaXN0KCkKCiMgU2V1cmF0CnBsb3RfbGlzdFtbMV1dID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gIlNldXJhdC5QaGFzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIlNldXJhdCBhbm5vdGF0aW9uIikgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgojIGN5Y2xvbmUKcGxvdF9saXN0W1syXV0gPSBTZXVyYXQ6OkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAiY3ljbG9uZS5QaGFzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gImN5Y2xvbmUgYW5ub3RhdGlvbiIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKIyBCSVJDNQpwbG90X2xpc3RbWzNdXSA9IFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSAiQklSQzUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJEKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGFxdWFyaXVzOjpjb2xvcl9nZW5lKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCiMgVE9QMkEKcGxvdF9saXN0W1s0XV0gPSBTZXVyYXQ6OkZlYXR1cmVQbG90KHNvYmosIGZlYXR1cmVzID0gIlRPUDJBIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogIGdncGxvdDI6OnNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSBhcXVhcml1czo6Y29sb3JfZ2VuZSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gMikKYGBgCgojIFNhdmUKCldlIHNhdmUgdGhlIFNldXJhdCBvYmplY3QgOgoKYGBge3Igc2F2ZV9zb2JqfQpzYXZlUkRTKHNvYmosIGZpbGUgPSBwYXN0ZTAob3V0X2RpciwgIi8iLCBzYXZlX25hbWUsICJfc29iai5yZHMiKSkKYGBgCgoKIyBSIFNlc3Npb24KCmBgYHtyIHNlc3Npb25pbmZvLCBlY2hvID0gRkFMU0UsIGZvbGRfb3V0cHV0ID0gVFJVRX0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==